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
17package android.support.v4.app;
18
19import android.app.RemoteInput;
20import android.content.ClipData;
21import android.content.ClipDescription;
22import android.content.Intent;
23import android.net.Uri;
24import android.os.Bundle;
25import android.support.annotation.RequiresApi;
26
27import java.util.HashMap;
28import java.util.Map;
29
30@RequiresApi(20)
31class 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
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
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
67    static Bundle getResultsFromIntent(Intent intent) {
68        return RemoteInput.getResultsFromIntent(intent);
69    }
70
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
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     */
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
149    private static String getExtraResultsKeyForData(String mimeType) {
150        return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
151    }
152
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