From 472a3b330a926c9d86a685dc0debf71037ea279b Mon Sep 17 00:00:00 2001 From: Shane Brennan Date: Mon, 12 Dec 2016 15:28:10 -0800 Subject: [PATCH] Add support for audio-focused notifications. Add extras keys and methos to Notification and RemoteInput for notifications whose contents have an audio representation and whose RemoteInputs can accept data results (useful for, e.g., voice messaging applications, photo results, etc). This motivator for this change is it enables an end-to-end flow for audio messaging including * System receives the audio message * Replying to the message with the user's voice * App receives the audio reply data Note that this CL just adds the capabilities, but does not add any UI or implementation to perform the above flow. Test: As agreed, this will be tested both with a set of unit tests, and with an "end-to-end" test that simulates two apps, one creating an audio message with audio reply action, and a second app that executes that action, providing an audio result. Change-Id: I55779ac4b781d8273c13c69fded0b56ee639cf01 --- api/current.txt | 7 + api/system-current.txt | 7 + api/test-current.txt | 7 + core/java/android/app/Notification.java | 87 ++++++++++- core/java/android/app/RemoteInput.java | 195 ++++++++++++++++++++---- 5 files changed, 272 insertions(+), 31 deletions(-) diff --git a/api/current.txt b/api/current.txt index 96c47ed52ca27..dd2aa48948958 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5025,6 +5025,7 @@ package android.app { field public static final int DEFAULT_LIGHTS = 4; // 0x4 field public static final int DEFAULT_SOUND = 1; // 0x1 field public static final int DEFAULT_VIBRATE = 2; // 0x2 + field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; @@ -5108,6 +5109,7 @@ package android.app { method public android.app.Notification.Action clone(); method public int describeContents(); method public boolean getAllowGeneratedReplies(); + method public android.app.RemoteInput[] getDataOnlyRemoteInputs(); method public android.os.Bundle getExtras(); method public android.graphics.drawable.Icon getIcon(); method public android.app.RemoteInput[] getRemoteInputs(); @@ -5573,14 +5575,18 @@ package android.app { } public final class RemoteInput implements android.os.Parcelable { + method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map); method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); method public int describeContents(); method public boolean getAllowFreeFormInput(); + method public java.util.Set getAllowedDataTypes(); method public java.lang.CharSequence[] getChoices(); + method public static java.util.Map getDataResultsFromIntent(android.content.Intent, java.lang.String); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); method public static android.os.Bundle getResultsFromIntent(android.content.Intent); + method public boolean isDataOnly(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; @@ -5592,6 +5598,7 @@ package android.app { method public android.app.RemoteInput.Builder addExtras(android.os.Bundle); method public android.app.RemoteInput build(); method public android.os.Bundle getExtras(); + method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean); method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); diff --git a/api/system-current.txt b/api/system-current.txt index 497b963c9b39e..4e8b4d5ce3173 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5183,6 +5183,7 @@ package android.app { field public static final int DEFAULT_LIGHTS = 4; // 0x4 field public static final int DEFAULT_SOUND = 1; // 0x1 field public static final int DEFAULT_VIBRATE = 2; // 0x2 + field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; @@ -5268,6 +5269,7 @@ package android.app { method public android.app.Notification.Action clone(); method public int describeContents(); method public boolean getAllowGeneratedReplies(); + method public android.app.RemoteInput[] getDataOnlyRemoteInputs(); method public android.os.Bundle getExtras(); method public android.graphics.drawable.Icon getIcon(); method public android.app.RemoteInput[] getRemoteInputs(); @@ -5758,14 +5760,18 @@ package android.app { } public final class RemoteInput implements android.os.Parcelable { + method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map); method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); method public int describeContents(); method public boolean getAllowFreeFormInput(); + method public java.util.Set getAllowedDataTypes(); method public java.lang.CharSequence[] getChoices(); + method public static java.util.Map getDataResultsFromIntent(android.content.Intent, java.lang.String); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); method public static android.os.Bundle getResultsFromIntent(android.content.Intent); + method public boolean isDataOnly(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; @@ -5777,6 +5783,7 @@ package android.app { method public android.app.RemoteInput.Builder addExtras(android.os.Bundle); method public android.app.RemoteInput build(); method public android.os.Bundle getExtras(); + method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean); method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); diff --git a/api/test-current.txt b/api/test-current.txt index 96ddfc793c2b3..3227ac767829b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5035,6 +5035,7 @@ package android.app { field public static final int DEFAULT_LIGHTS = 4; // 0x4 field public static final int DEFAULT_SOUND = 1; // 0x1 field public static final int DEFAULT_VIBRATE = 2; // 0x2 + field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText"; field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; @@ -5118,6 +5119,7 @@ package android.app { method public android.app.Notification.Action clone(); method public int describeContents(); method public boolean getAllowGeneratedReplies(); + method public android.app.RemoteInput[] getDataOnlyRemoteInputs(); method public android.os.Bundle getExtras(); method public android.graphics.drawable.Icon getIcon(); method public android.app.RemoteInput[] getRemoteInputs(); @@ -5584,14 +5586,18 @@ package android.app { } public final class RemoteInput implements android.os.Parcelable { + method public static void addDataResultToIntent(android.app.RemoteInput, android.content.Intent, java.util.Map); method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle); method public int describeContents(); method public boolean getAllowFreeFormInput(); + method public java.util.Set getAllowedDataTypes(); method public java.lang.CharSequence[] getChoices(); + method public static java.util.Map getDataResultsFromIntent(android.content.Intent, java.lang.String); method public android.os.Bundle getExtras(); method public java.lang.CharSequence getLabel(); method public java.lang.String getResultKey(); method public static android.os.Bundle getResultsFromIntent(android.content.Intent); + method public boolean isDataOnly(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; @@ -5603,6 +5609,7 @@ package android.app { method public android.app.RemoteInput.Builder addExtras(android.os.Bundle); method public android.app.RemoteInput build(); method public android.os.Bundle getExtras(); + method public android.app.RemoteInput.Builder setAllowDataType(java.lang.String, boolean); method public android.app.RemoteInput.Builder setAllowFreeFormInput(boolean); method public android.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]); method public android.app.RemoteInput.Builder setLabel(java.lang.CharSequence); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c6f0b661a65ac..ab0b68d61df5b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -988,6 +988,32 @@ public class Notification implements Parcelable */ public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; + /** + * {@link #extras} key: the audio contents of this notification. + * + * This is for use when rendering the notification on an audio-focused interface; + * the audio contents are a complete sound sample that contains the contents/body of the + * notification. This may be used in substitute of a Text-to-Speech reading of the + * notification. For example if the notification represents a voice message this should point + * to the audio of that message. + * + * The data stored under this key should be a String representation of a Uri that contains the + * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. + * + * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} + * has a field for holding data URI. That field can be used for audio. + * See {@code Message#setData}. + * + * Example usage: + *
+     * {@code
+     * Notification.Builder myBuilder = (build your Notification as normal);
+     * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString());
+     * }
+     * 
+ */ + public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; + /** @hide */ @SystemApi public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; @@ -1007,6 +1033,21 @@ public class Notification implements Parcelable * to attach actions. */ public static class Action implements Parcelable { + /** + * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of + * {@link RemoteInput}s. + * + * This is intended for {@link RemoteInput}s that only accept data, meaning + * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} + * is null or empty, and {@link RemoteInput#getAllowedDataTypes is non-null and not + * empty. These {@link RemoteInput}s will be ignored by devices that do not + * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. + * + * You can test if a RemoteInput matches these constraints using + * {@link RemoteInput#isDataOnly}. + */ + private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; + private final Bundle mExtras; private Icon mIcon; private final RemoteInput[] mRemoteInputs; @@ -1097,12 +1138,27 @@ public class Notification implements Parcelable /** * Get the list of inputs to be collected from the user when this action is sent. - * May return null if no remote inputs were added. + * May return null if no remote inputs were added. Only returns inputs which accept + * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. */ public RemoteInput[] getRemoteInputs() { return mRemoteInputs; } + /** + * Get the list of inputs to be collected from the user that ONLY accept data when this + * action is sent. These remote inputs are guaranteed to return true on a call to + * {@link RemoteInput#isDataOnly}. + * + * May return null if no data-only remote inputs were added. + * + * This method exists so that legacy RemoteInput collectors that pre-date the addition + * of non-textual RemoteInputs do not access these remote inputs. + */ + public RemoteInput[] getDataOnlyRemoteInputs() { + return (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS); + } + /** * Builder class for {@link Action} objects. */ @@ -1226,9 +1282,32 @@ public class Notification implements Parcelable * @return the built action */ public Action build() { - RemoteInput[] remoteInputs = mRemoteInputs != null - ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null; - return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs, + ArrayList dataOnlyInputs = new ArrayList<>(); + RemoteInput[] previousDataInputs = + (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS); + if (previousDataInputs == null) { + for (RemoteInput input : previousDataInputs) { + dataOnlyInputs.add(input); + } + } + List textInputs = new ArrayList<>(); + if (mRemoteInputs != null) { + for (RemoteInput input : mRemoteInputs) { + if (input.isDataOnly()) { + dataOnlyInputs.add(input); + } else { + textInputs.add(input); + } + } + } + if (!dataOnlyInputs.isEmpty()) { + RemoteInput[] dataInputsArr = + dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); + mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); + } + RemoteInput[] textInputsArr = textInputs.isEmpty() + ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); + return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, mAllowGeneratedReplies); } } diff --git a/core/java/android/app/RemoteInput.java b/core/java/android/app/RemoteInput.java index 11420c5ed4ffc..d1dc859314047 100644 --- a/core/java/android/app/RemoteInput.java +++ b/core/java/android/app/RemoteInput.java @@ -19,9 +19,14 @@ package android.app; import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; /** * A {@code RemoteInput} object specifies input to be collected from a user to be passed along with @@ -61,9 +66,13 @@ public final class RemoteInput implements Parcelable { /** Label used to denote the clip data type used for remote input transport */ public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results"; - /** Extra added to a clip data intent object to hold the results bundle. */ + /** Extra added to a clip data intent object to hold the text results bundle. */ public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData"; + /** Extra added to a clip data intent object to hold the data results bundle. */ + private static final String EXTRA_DATA_TYPE_RESULTS_DATA = + "android.remoteinput.dataTypeResultsData"; + // Flags bitwise-ored to mFlags private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1; @@ -75,14 +84,16 @@ public final class RemoteInput implements Parcelable { private final CharSequence[] mChoices; private final int mFlags; private final Bundle mExtras; + private final ArraySet mAllowedDataTypes; private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, - int flags, Bundle extras) { + int flags, Bundle extras, ArraySet allowedDataTypes) { this.mResultKey = resultKey; this.mLabel = label; this.mChoices = choices; this.mFlags = flags; this.mExtras = extras; + this.mAllowedDataTypes = allowedDataTypes; } /** @@ -107,6 +118,21 @@ public final class RemoteInput implements Parcelable { return mChoices; } + public Set getAllowedDataTypes() { + return mAllowedDataTypes; + } + + /** + * Returns true if the input only accepts data, meaning {@link #getAllowFreeFormInput} + * is false, {@link #getChoices} is null or empty, and {@link #getAllowedDataTypes is + * non-null and not empty. + */ + public boolean isDataOnly() { + return !getAllowFreeFormInput() + && (getChoices() == null || getChoices().length == 0) + && !getAllowedDataTypes().isEmpty(); + } + /** * Get whether or not users can provide an arbitrary value for * input. If you set this to {@code false}, users must select one of the @@ -133,6 +159,7 @@ public final class RemoteInput implements Parcelable { private CharSequence[] mChoices; private int mFlags = DEFAULT_FLAGS; private Bundle mExtras = new Bundle(); + private final ArraySet mAllowedDataTypes = new ArraySet<>(); /** * Create a builder object for {@link RemoteInput} objects. @@ -177,14 +204,34 @@ public final class RemoteInput implements Parcelable { /** * Specifies whether the user can provide arbitrary values. * - * @param allowFreeFormInput The default is {@code true}. - * If you specify {@code false}, you must provide a non-null - * and non-empty array to {@link #setChoices} or an + * @param mimeType A mime type that results are allowed to come in. + * Be aware that text results (see {@link #setAllowFreeFormInput} + * are allowed by default. If you do not want text results you will have to + * pass false to {@code setAllowFreeFormInput}. + * @param doAllow Whether the mime type should be allowed or not. + * @return this object for method chaining + */ + public Builder setAllowDataType(String mimeType, boolean doAllow) { + if (doAllow) { + mAllowedDataTypes.add(mimeType); + } else { + mAllowedDataTypes.remove(mimeType); + } + return this; + } + + /** + * Specifies whether the user can provide arbitrary text values. + * + * @param allowFreeFormTextInput The default is {@code true}. + * If you specify {@code false}, you must either provide a non-null + * and non-empty array to {@link #setChoices}, or enable a data result + * in {@code setAllowDataType}. Otherwise an * {@link IllegalArgumentException} is thrown. * @return this object for method chaining */ - public Builder setAllowFreeFormInput(boolean allowFreeFormInput) { - setFlag(mFlags, allowFreeFormInput); + public Builder setAllowFreeFormInput(boolean allowFreeFormTextInput) { + setFlag(mFlags, allowFreeFormTextInput); return this; } @@ -224,7 +271,8 @@ public final class RemoteInput implements Parcelable { * object. */ public RemoteInput build() { - return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras); + return new RemoteInput( + mResultKey, mLabel, mChoices, mFlags, mExtras, mAllowedDataTypes); } } @@ -234,32 +282,68 @@ public final class RemoteInput implements Parcelable { mChoices = in.readCharSequenceArray(); mFlags = in.readInt(); mExtras = in.readBundle(); + mAllowedDataTypes = (ArraySet) in.readArraySet(null); } /** - * Get the remote input results bundle from an intent. The returned Bundle will - * contain a key/value for every result key populated by remote input collector. - * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value. + * Similar as {@link #getResultsFromIntent} but retrieves data results for a + * specific RemoteInput result. To retrieve a value use: + *
+     * {@code
+     * Map results =
+     *     RemoteInput.getDataResultsFromIntent(intent, REMOTE_INPUT_KEY);
+     * if (results != null) {
+     *   Uri data = results.get(MIME_TYPE_OF_INTEREST);
+     * }
+     * }
+     * 
+ * @param intent The intent object that fired in response to an action or content intent + * which also had one or more remote input requested. + * @param remoteInputResultKey The result key for the RemoteInput you want results for. + */ + public static Map getDataResultsFromIntent( + Intent intent, String remoteInputResultKey) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + return null; + } + Map results = new HashMap<>(); + Bundle extras = clipDataIntent.getExtras(); + for (String key : extras.keySet()) { + if (key.startsWith(EXTRA_DATA_TYPE_RESULTS_DATA)) { + String mimeType = key.substring(EXTRA_DATA_TYPE_RESULTS_DATA.length()); + if (mimeType == null || mimeType.isEmpty()) { + continue; + } + Bundle bundle = clipDataIntent.getBundleExtra(key); + String uriStr = bundle.getString(remoteInputResultKey); + if (uriStr == null || uriStr.isEmpty()) { + continue; + } + results.put(mimeType, Uri.parse(uriStr)); + } + } + return results.isEmpty() ? null : results; + } + + /** + * Get the remote input text results bundle from an intent. The returned Bundle will + * contain a key/value for every result key populated with text by remote input collector. + * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value. For non-text + * results use {@link #getDataResultsFromIntent}. * @param intent The intent object that fired in response to an action or content intent * which also had one or more remote input requested. */ public static Bundle getResultsFromIntent(Intent intent) { - ClipData clipData = intent.getClipData(); - if (clipData == null) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { return null; } - ClipDescription clipDescription = clipData.getDescription(); - if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) { - return null; - } - if (clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) { - return clipData.getItemAt(0).getIntent().getExtras().getParcelable(EXTRA_RESULTS_DATA); - } - return null; + return clipDataIntent.getExtras().getParcelable(EXTRA_RESULTS_DATA); } /** - * Populate an intent object with the results gathered from remote input. This method + * Populate an intent object with the text results gathered from remote input. This method * should only be called by remote input collection services when sending results to a * pending intent. * @param remoteInputs The remote inputs for which results are being provided @@ -267,20 +351,61 @@ public final class RemoteInput implements Parcelable { * field of the intent will be modified to contain the results. * @param results A bundle holding the remote input results. This bundle should * be populated with keys matching the result keys specified in - * {@code remoteInputs} with values being the result per key. + * {@code remoteInputs} with values being the CharSequence results per key. */ public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent, Bundle results) { - Bundle resultsBundle = new Bundle(); + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + clipDataIntent = new Intent(); // First time we've added a result. + } + Bundle resultsBundle = clipDataIntent.getBundleExtra(EXTRA_RESULTS_DATA); + if (resultsBundle == null) { + resultsBundle = new Bundle(); + } for (RemoteInput remoteInput : remoteInputs) { Object result = results.get(remoteInput.getResultKey()); if (result instanceof CharSequence) { resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result); } } - Intent clipIntent = new Intent(); - clipIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle); - intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipIntent)); + clipDataIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle); + intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); + } + + /** + * Same as {@link #addResultsToIntent} but for setting data results. + * @param remoteInput The remote input for which results are being provided + * @param intent The intent to add remote input results to. The {@link ClipData} + * field of the intent will be modified to contain the results. + * @param results A map of mime type to the Uri result for that mime type. + */ + public static void addDataResultToIntent(RemoteInput remoteInput, Intent intent, + Map results) { + Intent clipDataIntent = getClipDataIntentFromIntent(intent); + if (clipDataIntent == null) { + clipDataIntent = new Intent(); // First time we've added a result. + } + for (Map.Entry entry : results.entrySet()) { + String mimeType = entry.getKey(); + Uri uri = entry.getValue(); + if (mimeType == null) { + continue; + } + Bundle resultsBundle = + clipDataIntent.getBundleExtra(getExtraResultsKeyForData(mimeType)); + if (resultsBundle == null) { + resultsBundle = new Bundle(); + } + resultsBundle.putString(remoteInput.getResultKey(), uri.toString()); + + clipDataIntent.putExtra(getExtraResultsKeyForData(mimeType), resultsBundle); + } + intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipDataIntent)); + } + + private static String getExtraResultsKeyForData(String mimeType) { + return EXTRA_DATA_TYPE_RESULTS_DATA + mimeType; } @Override @@ -295,6 +420,7 @@ public final class RemoteInput implements Parcelable { out.writeCharSequenceArray(mChoices); out.writeInt(mFlags); out.writeBundle(mExtras); + out.writeArraySet(mAllowedDataTypes); } public static final Creator CREATOR = new Creator() { @@ -308,4 +434,19 @@ public final class RemoteInput implements Parcelable { return new RemoteInput[size]; } }; + + private static Intent getClipDataIntentFromIntent(Intent intent) { + ClipData clipData = intent.getClipData(); + if (clipData == null) { + return null; + } + ClipDescription clipDescription = clipData.getDescription(); + if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) { + return null; + } + if (!clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) { + return null; + } + return clipData.getItemAt(0).getIntent(); + } }