• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app;
18 
19 import android.content.ClipData;
20 import android.content.ClipDescription;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 
26 /**
27  * A {@code RemoteInput} object specifies input to be collected from a user to be passed along with
28  * an intent inside a {@link android.app.PendingIntent} that is sent.
29  * Always use {@link RemoteInput.Builder} to create instances of this class.
30  * <p class="note"> See
31  * <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from
32  * a Notification</a> for more information on how to use this class.
33  *
34  * <p>The following example adds a {@code RemoteInput} to a {@link Notification.Action},
35  * sets the result key as {@code quick_reply}, and sets the label as {@code Quick reply}.
36  * Users are prompted to input a response when they trigger the action. The results are sent along
37  * with the intent and can be retrieved with the result key (provided to the {@link Builder}
38  * constructor) from the Bundle returned by {@link #getResultsFromIntent}.
39  *
40  * <pre class="prettyprint">
41  * public static final String KEY_QUICK_REPLY_TEXT = "quick_reply";
42  * Notification.Action action = new Notification.Action.Builder(
43  *         R.drawable.reply, &quot;Reply&quot;, actionIntent)
44  *         <b>.addRemoteInput(new RemoteInput.Builder(KEY_QUICK_REPLY_TEXT)
45  *                 .setLabel("Quick reply").build()</b>)
46  *         .build();</pre>
47  *
48  * <p>When the {@link android.app.PendingIntent} is fired, the intent inside will contain the
49  * input results if collected. To access these results, use the {@link #getResultsFromIntent}
50  * function. The result values will present under the result key passed to the {@link Builder}
51  * constructor.
52  *
53  * <pre class="prettyprint">
54  * public static final String KEY_QUICK_REPLY_TEXT = "quick_reply";
55  * Bundle results = RemoteInput.getResultsFromIntent(intent);
56  * if (results != null) {
57  *     CharSequence quickReplyResult = results.getCharSequence(KEY_QUICK_REPLY_TEXT);
58  * }</pre>
59  */
60 public final class RemoteInput implements Parcelable {
61     /** Label used to denote the clip data type used for remote input transport */
62     public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
63 
64     /** Extra added to a clip data intent object to hold the results bundle. */
65     public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
66 
67     // Flags bitwise-ored to mFlags
68     private static final int FLAG_ALLOW_FREE_FORM_INPUT = 0x1;
69 
70     // Default value for flags integer
71     private static final int DEFAULT_FLAGS = FLAG_ALLOW_FREE_FORM_INPUT;
72 
73     private final String mResultKey;
74     private final CharSequence mLabel;
75     private final CharSequence[] mChoices;
76     private final int mFlags;
77     private final Bundle mExtras;
78 
RemoteInput(String resultKey, CharSequence label, CharSequence[] choices, int flags, Bundle extras)79     private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
80             int flags, Bundle extras) {
81         this.mResultKey = resultKey;
82         this.mLabel = label;
83         this.mChoices = choices;
84         this.mFlags = flags;
85         this.mExtras = extras;
86     }
87 
88     /**
89      * Get the key that the result of this input will be set in from the Bundle returned by
90      * {@link #getResultsFromIntent} when the {@link android.app.PendingIntent} is sent.
91      */
getResultKey()92     public String getResultKey() {
93         return mResultKey;
94     }
95 
96     /**
97      * Get the label to display to users when collecting this input.
98      */
getLabel()99     public CharSequence getLabel() {
100         return mLabel;
101     }
102 
103     /**
104      * Get possible input choices. This can be {@code null} if there are no choices to present.
105      */
getChoices()106     public CharSequence[] getChoices() {
107         return mChoices;
108     }
109 
110     /**
111      * Get whether or not users can provide an arbitrary value for
112      * input. If you set this to {@code false}, users must select one of the
113      * choices in {@link #getChoices}. An {@link IllegalArgumentException} is thrown
114      * if you set this to false and {@link #getChoices} returns {@code null} or empty.
115      */
getAllowFreeFormInput()116     public boolean getAllowFreeFormInput() {
117         return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0;
118     }
119 
120     /**
121      * Get additional metadata carried around with this remote input.
122      */
getExtras()123     public Bundle getExtras() {
124         return mExtras;
125     }
126 
127     /**
128      * Builder class for {@link RemoteInput} objects.
129      */
130     public static final class Builder {
131         private final String mResultKey;
132         private CharSequence mLabel;
133         private CharSequence[] mChoices;
134         private int mFlags = DEFAULT_FLAGS;
135         private Bundle mExtras = new Bundle();
136 
137         /**
138          * Create a builder object for {@link RemoteInput} objects.
139          * @param resultKey the Bundle key that refers to this input when collected from the user
140          */
Builder(String resultKey)141         public Builder(String resultKey) {
142             if (resultKey == null) {
143                 throw new IllegalArgumentException("Result key can't be null");
144             }
145             mResultKey = resultKey;
146         }
147 
148         /**
149          * Set a label to be displayed to the user when collecting this input.
150          * @param label The label to show to users when they input a response.
151          * @return this object for method chaining
152          */
setLabel(CharSequence label)153         public Builder setLabel(CharSequence label) {
154             mLabel = Notification.safeCharSequence(label);
155             return this;
156         }
157 
158         /**
159          * Specifies choices available to the user to satisfy this input.
160          * @param choices an array of pre-defined choices for users input.
161          *        You must provide a non-null and non-empty array if
162          *        you disabled free form input using {@link #setAllowFreeFormInput}.
163          * @return this object for method chaining
164          */
setChoices(CharSequence[] choices)165         public Builder setChoices(CharSequence[] choices) {
166             if (choices == null) {
167                 mChoices = null;
168             } else {
169                 mChoices = new CharSequence[choices.length];
170                 for (int i = 0; i < choices.length; i++) {
171                     mChoices[i] = Notification.safeCharSequence(choices[i]);
172                 }
173             }
174             return this;
175         }
176 
177         /**
178          * Specifies whether the user can provide arbitrary values.
179          *
180          * @param allowFreeFormInput The default is {@code true}.
181          *         If you specify {@code false}, you must provide a non-null
182          *         and non-empty array to {@link #setChoices} or an
183          *         {@link IllegalArgumentException} is thrown.
184          * @return this object for method chaining
185          */
setAllowFreeFormInput(boolean allowFreeFormInput)186         public Builder setAllowFreeFormInput(boolean allowFreeFormInput) {
187             setFlag(mFlags, allowFreeFormInput);
188             return this;
189         }
190 
191         /**
192          * Merge additional metadata into this builder.
193          *
194          * <p>Values within the Bundle will replace existing extras values in this Builder.
195          *
196          * @see RemoteInput#getExtras
197          */
addExtras(Bundle extras)198         public Builder addExtras(Bundle extras) {
199             if (extras != null) {
200                 mExtras.putAll(extras);
201             }
202             return this;
203         }
204 
205         /**
206          * Get the metadata Bundle used by this Builder.
207          *
208          * <p>The returned Bundle is shared with this Builder.
209          */
getExtras()210         public Bundle getExtras() {
211             return mExtras;
212         }
213 
setFlag(int mask, boolean value)214         private void setFlag(int mask, boolean value) {
215             if (value) {
216                 mFlags |= mask;
217             } else {
218                 mFlags &= ~mask;
219             }
220         }
221 
222         /**
223          * Combine all of the options that have been set and return a new {@link RemoteInput}
224          * object.
225          */
build()226         public RemoteInput build() {
227             return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras);
228         }
229     }
230 
RemoteInput(Parcel in)231     private RemoteInput(Parcel in) {
232         mResultKey = in.readString();
233         mLabel = in.readCharSequence();
234         mChoices = in.readCharSequenceArray();
235         mFlags = in.readInt();
236         mExtras = in.readBundle();
237     }
238 
239     /**
240      * Get the remote input results bundle from an intent. The returned Bundle will
241      * contain a key/value for every result key populated by remote input collector.
242      * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value.
243      * @param intent The intent object that fired in response to an action or content intent
244      *               which also had one or more remote input requested.
245      */
getResultsFromIntent(Intent intent)246     public static Bundle getResultsFromIntent(Intent intent) {
247         ClipData clipData = intent.getClipData();
248         if (clipData == null) {
249             return null;
250         }
251         ClipDescription clipDescription = clipData.getDescription();
252         if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
253             return null;
254         }
255         if (clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) {
256             return clipData.getItemAt(0).getIntent().getExtras().getParcelable(EXTRA_RESULTS_DATA);
257         }
258         return null;
259     }
260 
261     /**
262      * Populate an intent object with the results gathered from remote input. This method
263      * should only be called by remote input collection services when sending results to a
264      * pending intent.
265      * @param remoteInputs The remote inputs for which results are being provided
266      * @param intent The intent to add remote inputs to. The {@link ClipData}
267      *               field of the intent will be modified to contain the results.
268      * @param results A bundle holding the remote input results. This bundle should
269      *                be populated with keys matching the result keys specified in
270      *                {@code remoteInputs} with values being the result per key.
271      */
addResultsToIntent(RemoteInput[] remoteInputs, Intent intent, Bundle results)272     public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,
273             Bundle results) {
274         Bundle resultsBundle = new Bundle();
275         for (RemoteInput remoteInput : remoteInputs) {
276             Object result = results.get(remoteInput.getResultKey());
277             if (result instanceof CharSequence) {
278                 resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result);
279             }
280         }
281         Intent clipIntent = new Intent();
282         clipIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle);
283         intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipIntent));
284     }
285 
286     @Override
describeContents()287     public int describeContents() {
288         return 0;
289     }
290 
291     @Override
writeToParcel(Parcel out, int flags)292     public void writeToParcel(Parcel out, int flags) {
293         out.writeString(mResultKey);
294         out.writeCharSequence(mLabel);
295         out.writeCharSequenceArray(mChoices);
296         out.writeInt(mFlags);
297         out.writeBundle(mExtras);
298     }
299 
300     public static final Creator<RemoteInput> CREATOR = new Creator<RemoteInput>() {
301         @Override
302         public RemoteInput createFromParcel(Parcel in) {
303             return new RemoteInput(in);
304         }
305 
306         @Override
307         public RemoteInput[] newArray(int size) {
308             return new RemoteInput[size];
309         }
310     };
311 }
312