1ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen/*
2ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Copyright (C) 2014 The Android Open Source Project
3ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
4ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Licensed under the Apache License, Version 2.0 (the "License");
5ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * you may not use this file except in compliance with the License.
6ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * You may obtain a copy of the License at
7ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
8ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *      http://www.apache.org/licenses/LICENSE-2.0
9ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen *
10ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * Unless required by applicable law or agreed to in writing, software
11ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * distributed under the License is distributed on an "AS IS" BASIS,
12ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * See the License for the specific language governing permissions and
14ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen * limitations under the License.
15ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen */
16ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
17ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenpackage android.support.v4.app;
18ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
19ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.app.RemoteInput;
2059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennanimport android.content.ClipData;
2159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennanimport android.content.ClipDescription;
22ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.content.Intent;
2359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennanimport android.net.Uri;
24ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenimport android.os.Bundle;
258f886fe8c7e23fe6ccb8734167c960c2ed3429c3Alan Viveretteimport android.support.annotation.RequiresApi;
26ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
2759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennanimport java.util.HashMap;
2859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennanimport java.util.Map;
2959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan
308f886fe8c7e23fe6ccb8734167c960c2ed3429c3Alan Viverette@RequiresApi(20)
31ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazenclass RemoteInputCompatApi20 {
3259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    /** Extra added to a clip data intent object to hold the data results bundle. */
3359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    private static final String EXTRA_DATA_TYPE_RESULTS_DATA =
3459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            "android.remoteinput.dataTypeResultsData";
3559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan
36ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static RemoteInputCompatBase.RemoteInput[] toCompat(RemoteInput[] srcArray,
37ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            RemoteInputCompatBase.RemoteInput.Factory factory) {
38ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        if (srcArray == null) {
39ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return null;
40ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
41ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        RemoteInputCompatBase.RemoteInput[] result = factory.newArray(srcArray.length);
42ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        for (int i = 0; i < srcArray.length; i++) {
43ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            RemoteInput src = srcArray[i];
44ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            result[i] = factory.build(src.getResultKey(), src.getLabel(), src.getChoices(),
4559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                    src.getAllowFreeFormInput(), src.getExtras(), null);
46ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
47ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        return result;
48ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
49ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
50ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static RemoteInput[] fromCompat(RemoteInputCompatBase.RemoteInput[] srcArray) {
51ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        if (srcArray == null) {
52ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            return null;
53ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
54ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        RemoteInput[] result = new RemoteInput[srcArray.length];
55ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        for (int i = 0; i < srcArray.length; i++) {
56ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            RemoteInputCompatBase.RemoteInput src = srcArray[i];
57ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            result[i] = new RemoteInput.Builder(src.getResultKey())
58ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    .setLabel(src.getLabel())
59ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    .setChoices(src.getChoices())
60ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    .setAllowFreeFormInput(src.getAllowFreeFormInput())
612bb98d48fdaf79a2bbd9d247da81a2bb9834dfc7Griff Hazen                    .addExtras(src.getExtras())
62ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen                    .build();
63ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        }
64ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        return result;
65ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
66ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
67ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static Bundle getResultsFromIntent(Intent intent) {
68ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen        return RemoteInput.getResultsFromIntent(intent);
69ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
70ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen
7159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    static Map<String, Uri> getDataResultsFromIntent(Intent intent, String remoteInputResultKey) {
7259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        Intent clipDataIntent = getClipDataIntentFromIntent(intent);
7359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        if (clipDataIntent == null) {
7459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            return null;
7559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
7659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        Map<String, Uri> results = new HashMap<>();
7759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        Bundle extras = clipDataIntent.getExtras();
7859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        for (String key : extras.keySet()) {
7959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            if (key.startsWith(EXTRA_DATA_TYPE_RESULTS_DATA)) {
8059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                String mimeType = key.substring(EXTRA_DATA_TYPE_RESULTS_DATA.length());
8159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                if (mimeType == null || mimeType.isEmpty()) {
8259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                    continue;
8359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                }
8459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                Bundle bundle = clipDataIntent.getBundleExtra(key);
8559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                String uriStr = bundle.getString(remoteInputResultKey);
8659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                if (uriStr == null || uriStr.isEmpty()) {
8759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                    continue;
8859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                }
8959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                results.put(mimeType, Uri.parse(uriStr));
9059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            }
9159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
9259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        return results.isEmpty() ? null : results;
9359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    }
9459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan
95ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    static void addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs,
96ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen            Intent intent, Bundle results) {
9759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        // Implementations of RemoteInput#addResultsToIntent prior to SDK 26 don't actually add
9859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        // results, they wipe out old results and insert the new one. Work around that by preserving
9959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        // old results.
10059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        Bundle existingTextResults = getResultsFromIntent(intent);
10159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        if (existingTextResults == null) {
10259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            existingTextResults = results;
10359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        } else {
10459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            existingTextResults.putAll(results);
10559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
10659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        for (RemoteInputCompatBase.RemoteInput input : remoteInputs) {
10759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            // Data results are also wiped out. So grab them and add them back in.
10859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            Map<String, Uri> existingDataResults =
10959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                    getDataResultsFromIntent(intent, input.getResultKey());
11059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            RemoteInputCompatBase.RemoteInput[] arr = new RemoteInputCompatBase.RemoteInput[1];
11159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            arr[0] = input;
11259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            RemoteInput.addResultsToIntent(fromCompat(arr), intent, existingTextResults);
11359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            if (existingDataResults != null) {
11459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                addDataResultToIntent(input, intent, existingDataResults);
11559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            }
11659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
11759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    }
11859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan
11959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    /**
12059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan     * Same as {@link #addResultsToIntent} but for setting data results.
12159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan     * @param remoteInput The remote input for which results are being provided
12259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan     * @param intent The intent to add remote input results to. The {@link ClipData}
12359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan     *               field of the intent will be modified to contain the results.
12459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan     * @param results A map of mime type to the Uri result for that mime type.
12559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan     */
12659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    public static void addDataResultToIntent(RemoteInputCompatBase.RemoteInput remoteInput,
12759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            Intent intent, Map<String, Uri> results) {
12859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        Intent clipDataIntent = getClipDataIntentFromIntent(intent);
12959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        if (clipDataIntent == null) {
13059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            clipDataIntent = new Intent();  // First time we've added a result.
13159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
13259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        for (Map.Entry<String, Uri> entry : results.entrySet()) {
13359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            String mimeType = entry.getKey();
13459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            Uri uri = entry.getValue();
13559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            if (mimeType == null) {
13659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                continue;
13759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            }
13859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            Bundle resultsBundle =
13959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                    clipDataIntent.getBundleExtra(getExtraResultsKeyForData(mimeType));
14059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            if (resultsBundle == null) {
14159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan                resultsBundle = new Bundle();
14259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            }
14359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            resultsBundle.putString(remoteInput.getResultKey(), uri.toString());
14459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle);
14559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
14659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        intent.setClipData(ClipData.newIntent(RemoteInput.RESULTS_CLIP_LABEL, clipDataIntent));
14759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    }
14859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan
14959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    private static String getExtraResultsKeyForData(String mimeType) {
15059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType;
15159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    }
15259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan
15359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan    private static Intent getClipDataIntentFromIntent(Intent intent) {
15459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        ClipData clipData = intent.getClipData();
15559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        if (clipData == null) {
15659d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            return null;
15759d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
15859d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        ClipDescription clipDescription = clipData.getDescription();
15959d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
16059d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            return null;
16159d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
16259d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        if (!clipDescription.getLabel().equals(RemoteInput.RESULTS_CLIP_LABEL)) {
16359d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan            return null;
16459d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        }
16559d562bdab983469340a054bd3bc888034fa6bc8Shane Brennan        return clipData.getItemAt(0).getIntent();
166ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen    }
167ce16e4276c2f61109a23b3f6707cfcd87b07c735Griff Hazen}
168