blob: 11420c5ed4ffc6ee8eee55af71d410ece96df23d [file] [log] [blame]
Griff Hazen5cadc3b2014-05-20 09:55:39 -07001/*
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.app;
18
19import android.content.ClipData;
20import android.content.ClipDescription;
21import android.content.Intent;
22import android.os.Bundle;
23import android.os.Parcel;
24import 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 */
60public 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
Griff Hazenc3104152014-05-22 14:38:36 -070067 // 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
Griff Hazen5cadc3b2014-05-20 09:55:39 -070073 private final String mResultKey;
74 private final CharSequence mLabel;
75 private final CharSequence[] mChoices;
Griff Hazenc3104152014-05-22 14:38:36 -070076 private final int mFlags;
Griff Hazen5cadc3b2014-05-20 09:55:39 -070077 private final Bundle mExtras;
78
79 private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
Griff Hazenc3104152014-05-22 14:38:36 -070080 int flags, Bundle extras) {
Griff Hazen5cadc3b2014-05-20 09:55:39 -070081 this.mResultKey = resultKey;
82 this.mLabel = label;
83 this.mChoices = choices;
Griff Hazenc3104152014-05-22 14:38:36 -070084 this.mFlags = flags;
Griff Hazen5cadc3b2014-05-20 09:55:39 -070085 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 */
92 public String getResultKey() {
93 return mResultKey;
94 }
95
96 /**
97 * Get the label to display to users when collecting this input.
98 */
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 */
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 */
116 public boolean getAllowFreeFormInput() {
Griff Hazenc3104152014-05-22 14:38:36 -0700117 return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0;
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700118 }
119
120 /**
121 * Get additional metadata carried around with this remote input.
122 */
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;
Griff Hazenc3104152014-05-22 14:38:36 -0700134 private int mFlags = DEFAULT_FLAGS;
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700135 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 */
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 */
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
Griff Hazen9e1379f2014-05-20 12:50:51 -0700162 * you disabled free form input using {@link #setAllowFreeFormInput}.
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700163 * @return this object for method chaining
164 */
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 */
186 public Builder setAllowFreeFormInput(boolean allowFreeFormInput) {
Griff Hazenc3104152014-05-22 14:38:36 -0700187 setFlag(mFlags, allowFreeFormInput);
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700188 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 */
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 */
210 public Bundle getExtras() {
211 return mExtras;
212 }
213
Griff Hazenc3104152014-05-22 14:38:36 -0700214 private void setFlag(int mask, boolean value) {
215 if (value) {
216 mFlags |= mask;
217 } else {
218 mFlags &= ~mask;
219 }
220 }
221
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700222 /**
223 * Combine all of the options that have been set and return a new {@link RemoteInput}
224 * object.
225 */
226 public RemoteInput build() {
Griff Hazenc3104152014-05-22 14:38:36 -0700227 return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras);
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700228 }
229 }
230
231 private RemoteInput(Parcel in) {
232 mResultKey = in.readString();
233 mLabel = in.readCharSequence();
234 mChoices = in.readCharSequenceArray();
Griff Hazenc3104152014-05-22 14:38:36 -0700235 mFlags = in.readInt();
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700236 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 */
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 */
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
287 public int describeContents() {
288 return 0;
289 }
290
291 @Override
292 public void writeToParcel(Parcel out, int flags) {
293 out.writeString(mResultKey);
294 out.writeCharSequence(mLabel);
295 out.writeCharSequenceArray(mChoices);
Griff Hazenc3104152014-05-22 14:38:36 -0700296 out.writeInt(mFlags);
Griff Hazen5cadc3b2014-05-20 09:55:39 -0700297 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}