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.app.RemoteInput;
20 import android.content.ClipData;
21 import android.content.ClipDescription;
22 import android.content.Intent;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.support.annotation.RequiresApi;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 
30 @RequiresApi(20)
31 class RemoteInputCompatApi20 {
32     /** Extra added to a clip data intent object to hold the data results bundle. */
33     private static final String EXTRA_DATA_TYPE_RESULTS_DATA =
34             "android.remoteinput.dataTypeResultsData";
35 
toCompat(RemoteInput[] srcArray, RemoteInputCompatBase.RemoteInput.Factory factory)36     static RemoteInputCompatBase.RemoteInput[] toCompat(RemoteInput[] srcArray,
37             RemoteInputCompatBase.RemoteInput.Factory factory) {
38         if (srcArray == null) {
39             return null;
40         }
41         RemoteInputCompatBase.RemoteInput[] result = factory.newArray(srcArray.length);
42         for (int i = 0; i < srcArray.length; i++) {
43             RemoteInput src = srcArray[i];
44             result[i] = factory.build(src.getResultKey(), src.getLabel(), src.getChoices(),
45                     src.getAllowFreeFormInput(), src.getExtras(), null);
46         }
47         return result;
48     }
49 
fromCompat(RemoteInputCompatBase.RemoteInput[] srcArray)50     static RemoteInput[] fromCompat(RemoteInputCompatBase.RemoteInput[] srcArray) {
51         if (srcArray == null) {
52             return null;
53         }
54         RemoteInput[] result = new RemoteInput[srcArray.length];
55         for (int i = 0; i < srcArray.length; i++) {
56             RemoteInputCompatBase.RemoteInput src = srcArray[i];
57             result[i] = new RemoteInput.Builder(src.getResultKey())
58                     .setLabel(src.getLabel())
59                     .setChoices(src.getChoices())
60                     .setAllowFreeFormInput(src.getAllowFreeFormInput())
61                     .addExtras(src.getExtras())
62                     .build();
63         }
64         return result;
65     }
66 
getResultsFromIntent(Intent intent)67     static Bundle getResultsFromIntent(Intent intent) {
68         return RemoteInput.getResultsFromIntent(intent);
69     }
70 
getDataResultsFromIntent(Intent intent, String remoteInputResultKey)71     static Map<String, Uri> getDataResultsFromIntent(Intent intent, String remoteInputResultKey) {
72         Intent clipDataIntent = getClipDataIntentFromIntent(intent);
73         if (clipDataIntent == null) {
74             return null;
75         }
76         Map<String, Uri> results = new HashMap<>();
77         Bundle extras = clipDataIntent.getExtras();
78         for (String key : extras.keySet()) {
79             if (key.startsWith(EXTRA_DATA_TYPE_RESULTS_DATA)) {
80                 String mimeType = key.substring(EXTRA_DATA_TYPE_RESULTS_DATA.length());
81                 if (mimeType == null || mimeType.isEmpty()) {
82                     continue;
83                 }
84                 Bundle bundle = clipDataIntent.getBundleExtra(key);
85                 String uriStr = bundle.getString(remoteInputResultKey);
86                 if (uriStr == null || uriStr.isEmpty()) {
87                     continue;
88                 }
89                 results.put(mimeType, Uri.parse(uriStr));
90             }
91         }
92         return results.isEmpty() ? null : results;
93     }
94 
addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs, Intent intent, Bundle results)95     static void addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs,
96             Intent intent, Bundle results) {
97         // Implementations of RemoteInput#addResultsToIntent prior to SDK 26 don't actually add
98         // results, they wipe out old results and insert the new one. Work around that by preserving
99         // old results.
100         Bundle existingTextResults = getResultsFromIntent(intent);
101         if (existingTextResults == null) {
102             existingTextResults = results;
103         } else {
104             existingTextResults.putAll(results);
105         }
106         for (RemoteInputCompatBase.RemoteInput input : remoteInputs) {
107             // Data results are also wiped out. So grab them and add them back in.
108             Map<String, Uri> existingDataResults =
109                     getDataResultsFromIntent(intent, input.getResultKey());
110             RemoteInputCompatBase.RemoteInput[] arr = new RemoteInputCompatBase.RemoteInput[1];
111             arr[0] = input;
112             RemoteInput.addResultsToIntent(fromCompat(arr), intent, existingTextResults);
113             if (existingDataResults != null) {
114                 addDataResultToIntent(input, intent, existingDataResults);
115             }
116         }
117     }
118 
119     /**
120      * Same as {@link #addResultsToIntent} but for setting data results.
121      * @param remoteInput The remote input for which results are being provided
122      * @param intent The intent to add remote input results to. The {@link ClipData}
123      *               field of the intent will be modified to contain the results.
124      * @param results A map of mime type to the Uri result for that mime type.
125      */
addDataResultToIntent(RemoteInputCompatBase.RemoteInput remoteInput, Intent intent, Map<String, Uri> results)126     public static void addDataResultToIntent(RemoteInputCompatBase.RemoteInput remoteInput,
127             Intent intent, Map<String, Uri> results) {
128         Intent clipDataIntent = getClipDataIntentFromIntent(intent);
129         if (clipDataIntent == null) {
130             clipDataIntent = new Intent();  // First time we've added a result.
131         }
132         for (Map.Entry<String, Uri> entry : results.entrySet()) {
133             String mimeType = entry.getKey();
134             Uri uri = entry.getValue();
135             if (mimeType == null) {
136                 continue;
137             }
138             Bundle resultsBundle =
139                     clipDataIntent.getBundleExtra(getExtraResultsKeyForData(mimeType));
140             if (resultsBundle == null) {
141                 resultsBundle = new Bundle();
142             }
143             resultsBundle.putString(remoteInput.getResultKey(), uri.toString());
144             clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle);
145         }
146         intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
147     }
148 
getExtraResultsKeyForData(String mimeType)149     private static String getExtraResultsKeyForData(String mimeType) {
150         return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
151     }
152 
getClipDataIntentFromIntent(Intent intent)153     private static Intent getClipDataIntentFromIntent(Intent intent) {
154         ClipData clipData = intent.getClipData();
155         if (clipData == null) {
156             return null;
157         }
158         ClipDescription clipDescription = clipData.getDescription();
159         if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
160             return null;
161         }
162         if (!clipDescription.getLabel().equals(RemoteInput.RESULTS_CLIP_LABEL)) {
163             return null;
164         }
165         return clipData.getItemAt(0).getIntent();
166     }
167 }
168