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.content.ClipData;
20import android.content.ClipDescription;
21import android.content.Intent;
22import android.net.Uri;
23import android.os.Bundle;
24import android.support.annotation.RequiresApi;
25
26import java.util.ArrayList;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Map;
30import java.util.Set;
31
32@RequiresApi(16)
33class 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
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
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
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
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
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
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
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     */
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
192    private static String getExtraResultsKeyForData(String mimeType) {
193        return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
194    }
195
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