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, "Reply", 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 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 */ 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() { 117 return (mFlags & FLAG_ALLOW_FREE_FORM_INPUT) != 0; 118 } 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; 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 */ 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 162 * you disabled free form input using {@link #setAllowFreeFormInput}. 163 * @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) { 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 */ 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 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 */ 226 public RemoteInput build() { 227 return new RemoteInput(mResultKey, mLabel, mChoices, mFlags, mExtras); 228 } 229 } 230 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 */ 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); 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