Home | History | Annotate | Download | only in app
      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 
     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