1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.support.v4.app; 18 19 import android.content.ClipData; 20 import android.content.ClipDescription; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.support.annotation.RequiresApi; 25 26 import java.util.ArrayList; 27 import java.util.HashMap; 28 import java.util.HashSet; 29 import java.util.Map; 30 import java.util.Set; 31 32 @RequiresApi(16) 33 class RemoteInputCompatJellybean { 34 /** Label used to denote the clip data type used for remote input transport */ 35 public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results"; 36 37 /** Extra added to a clip data intent object to hold the results bundle. */ 38 public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; 39 40 /** Extra added to a clip data intent object to hold the data results bundle. */ 41 private static final String EXTRA_DATA_TYPE_RESULTS_DATA = 42 "android.remoteinput.dataTypeResultsData"; 43 44 private static final String KEY_RESULT_KEY = "resultKey"; 45 private static final String KEY_LABEL = "label"; 46 private static final String KEY_CHOICES = "choices"; 47 private static final String KEY_ALLOW_FREE_FORM_INPUT = "allowFreeFormInput"; 48 private static final String KEY_EXTRAS = "extras"; 49 private static final String KEY_ALLOWED_DATA_TYPES = "allowedDataTypes"; 50 fromBundle(Bundle data, RemoteInputCompatBase.RemoteInput.Factory factory)51 static RemoteInputCompatBase.RemoteInput fromBundle(Bundle data, 52 RemoteInputCompatBase.RemoteInput.Factory factory) { 53 ArrayList<String> allowedDataTypesAsList = data.getStringArrayList(KEY_ALLOWED_DATA_TYPES); 54 Set<String> allowedDataTypes = new HashSet<>(); 55 if (allowedDataTypesAsList != null) { 56 for (String type : allowedDataTypesAsList) { 57 allowedDataTypes.add(type); 58 } 59 } 60 return factory.build(data.getString(KEY_RESULT_KEY), 61 data.getCharSequence(KEY_LABEL), 62 data.getCharSequenceArray(KEY_CHOICES), 63 data.getBoolean(KEY_ALLOW_FREE_FORM_INPUT), 64 data.getBundle(KEY_EXTRAS), 65 allowedDataTypes); 66 } 67 toBundle(RemoteInputCompatBase.RemoteInput remoteInput)68 static Bundle toBundle(RemoteInputCompatBase.RemoteInput remoteInput) { 69 Bundle data = new Bundle(); 70 data.putString(KEY_RESULT_KEY, remoteInput.getResultKey()); 71 data.putCharSequence(KEY_LABEL, remoteInput.getLabel()); 72 data.putCharSequenceArray(KEY_CHOICES, remoteInput.getChoices()); 73 data.putBoolean(KEY_ALLOW_FREE_FORM_INPUT, remoteInput.getAllowFreeFormInput()); 74 data.putBundle(KEY_EXTRAS, remoteInput.getExtras()); 75 76 Set<String> allowedDataTypes = remoteInput.getAllowedDataTypes(); 77 if (allowedDataTypes != null && !allowedDataTypes.isEmpty()) { 78 ArrayList<String> allowedDataTypesAsList = new ArrayList<>(allowedDataTypes.size()); 79 for (String type : allowedDataTypes) { 80 allowedDataTypesAsList.add(type); 81 } 82 data.putStringArrayList(KEY_ALLOWED_DATA_TYPES, allowedDataTypesAsList); 83 } 84 return data; 85 } 86 fromBundleArray(Bundle[] bundles, RemoteInputCompatBase.RemoteInput.Factory factory)87 static RemoteInputCompatBase.RemoteInput[] fromBundleArray(Bundle[] bundles, 88 RemoteInputCompatBase.RemoteInput.Factory factory) { 89 if (bundles == null) { 90 return null; 91 } 92 RemoteInputCompatBase.RemoteInput[] remoteInputs = factory.newArray(bundles.length); 93 for (int i = 0; i < bundles.length; i++) { 94 remoteInputs[i] = fromBundle(bundles[i], factory); 95 } 96 return remoteInputs; 97 } 98 toBundleArray(RemoteInputCompatBase.RemoteInput[] remoteInputs)99 static Bundle[] toBundleArray(RemoteInputCompatBase.RemoteInput[] remoteInputs) { 100 if (remoteInputs == null) { 101 return null; 102 } 103 Bundle[] bundles = new Bundle[remoteInputs.length]; 104 for (int i = 0; i < remoteInputs.length; i++) { 105 bundles[i] = toBundle(remoteInputs[i]); 106 } 107 return bundles; 108 } 109 getResultsFromIntent(Intent intent)110 static Bundle getResultsFromIntent(Intent intent) { 111 Intent clipDataIntent = getClipDataIntentFromIntent(intent); 112 if (clipDataIntent == null) { 113 return null; 114 } 115 return clipDataIntent.getExtras().getParcelable(EXTRA_RESULTS_DATA); 116 } 117 getDataResultsFromIntent(Intent intent, String remoteInputResultKey)118 static Map<String, Uri> getDataResultsFromIntent(Intent intent, String remoteInputResultKey) { 119 Intent clipDataIntent = getClipDataIntentFromIntent(intent); 120 if (clipDataIntent == null) { 121 return null; 122 } 123 Map<String, Uri> results = new HashMap<>(); 124 Bundle extras = clipDataIntent.getExtras(); 125 for (String key : extras.keySet()) { 126 if (key.startsWith(EXTRA_DATA_TYPE_RESULTS_DATA)) { 127 String mimeType = key.substring(EXTRA_DATA_TYPE_RESULTS_DATA.length()); 128 if (mimeType == null || mimeType.isEmpty()) { 129 continue; 130 } 131 Bundle bundle = clipDataIntent.getBundleExtra(key); 132 String uriStr = bundle.getString(remoteInputResultKey); 133 if (uriStr == null || uriStr.isEmpty()) { 134 continue; 135 } 136 results.put(mimeType, Uri.parse(uriStr)); 137 } 138 } 139 return results.isEmpty() ? null : results; 140 } 141 addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs, Intent intent, Bundle results)142 static void addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs, Intent intent, 143 Bundle results) { 144 Intent clipDataIntent = getClipDataIntentFromIntent(intent); 145 if (clipDataIntent == null) { 146 clipDataIntent = new Intent(); // First time we've added a result. 147 } 148 Bundle resultsBundle = clipDataIntent.getBundleExtra(EXTRA_RESULTS_DATA); 149 if (resultsBundle == null) { 150 resultsBundle = new Bundle(); 151 } 152 for (RemoteInputCompatBase.RemoteInput remoteInput : remoteInputs) { 153 Object result = results.get(remoteInput.getResultKey()); 154 if (result instanceof CharSequence) { 155 resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result); 156 } 157 } 158 clipDataIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle); 159 intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); 160 } 161 162 /** 163 * Same as {@link #addResultsToIntent} but for setting data results. 164 * @param remoteInput The remote input for which results are being provided 165 * @param intent The intent to add remote input results to. The {@link ClipData} 166 * field of the intent will be modified to contain the results. 167 * @param results A map of mime type to the Uri result for that mime type. 168 */ addDataResultToIntent(RemoteInput remoteInput, Intent intent, Map<String, Uri> results)169 public static void addDataResultToIntent(RemoteInput remoteInput, Intent intent, 170 Map<String, Uri> results) { 171 Intent clipDataIntent = getClipDataIntentFromIntent(intent); 172 if (clipDataIntent == null) { 173 clipDataIntent = new Intent(); // First time we've added a result. 174 } 175 for (Map.Entry<String, Uri> entry : results.entrySet()) { 176 String mimeType = entry.getKey(); 177 Uri uri = entry.getValue(); 178 if (mimeType == null) { 179 continue; 180 } 181 Bundle resultsBundle = 182 clipDataIntent.getBundleExtra(getExtraResultsKeyForData(mimeType)); 183 if (resultsBundle == null) { 184 resultsBundle = new Bundle(); 185 } 186 resultsBundle.putString(remoteInput.getResultKey(), uri.toString()); 187 clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle); 188 } 189 intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); 190 } 191 getExtraResultsKeyForData(String mimeType)192 private static String getExtraResultsKeyForData(String mimeType) { 193 return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType; 194 } 195 getClipDataIntentFromIntent(Intent intent)196 private static Intent getClipDataIntentFromIntent(Intent intent) { 197 ClipData clipData = intent.getClipData(); 198 if (clipData == null) { 199 return null; 200 } 201 ClipDescription clipDescription = clipData.getDescription(); 202 if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) { 203 return null; 204 } 205 if (!clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) { 206 return null; 207 } 208 return clipData.getItemAt(0).getIntent(); 209 } 210 } 211