ChooserTarget.java revision c6d5e3a406c0e80638304980bac13abaa703a9a0
1/*
2 * Copyright (C) 2015 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
18package android.service.chooser;
19
20import android.app.Activity;
21import android.app.PendingIntent;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.IntentSender;
27import android.graphics.Bitmap;
28import android.os.Bundle;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.os.UserHandle;
32import android.util.Log;
33
34/**
35 * A ChooserTarget represents a deep-link into an application as returned by a
36 * {@link android.service.chooser.ChooserTargetService}.
37 */
38public final class ChooserTarget implements Parcelable {
39    private static final String TAG = "ChooserTarget";
40
41    /**
42     * The title of this target that will be shown to the user. The title may be truncated
43     * if it is too long to display in the space provided.
44     */
45    private CharSequence mTitle;
46
47    /**
48     * The icon that will be shown to the user to represent this target.
49     * The system may resize this icon as appropriate.
50     */
51    private Bitmap mIcon;
52
53    /**
54     * The IntentSender that will be used to deliver the intent to the target.
55     * It will be {@link android.content.Intent#fillIn(android.content.Intent, int)} filled in}
56     * by the real intent sent by the application.
57     */
58    private IntentSender mIntentSender;
59
60    /**
61     * A raw intent provided in lieu of an IntentSender. Will be filled in and sent
62     * by {@link #sendIntent(Context, Intent)}.
63     */
64    private Intent mIntent;
65
66    /**
67     * The score given to this item. It can be normalized.
68     */
69    private float mScore;
70
71    /**
72     * Construct a deep link target for presentation by a chooser UI.
73     *
74     * <p>A target is composed of a title and an icon for presentation to the user.
75     * The UI presenting this target may truncate the title if it is too long to be presented
76     * in the available space, as well as crop, resize or overlay the supplied icon.</p>
77     *
78     * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
79     * to the other targets supplied by the same
80     * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
81     * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).
82     * Scores for a set of targets do not need to sum to 1.</p>
83     *
84     * <p>Before being sent, the PendingIntent supplied will be
85     * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
86     * to the chooser. When constructing a PendingIntent for use in a ChooserTarget, make sure
87     * that you permit the relevant fields to be filled in using the appropriate flags such as
88     * {@link Intent#FILL_IN_ACTION}, {@link Intent#FILL_IN_CATEGORIES},
89     * {@link Intent#FILL_IN_DATA} and {@link Intent#FILL_IN_CLIP_DATA}. Note that
90     * {@link Intent#FILL_IN_CLIP_DATA} is required to appropriately receive URI permission grants
91     * for {@link Intent#ACTION_SEND} intents.</p>
92     *
93     * <p>Take care not to place custom {@link android.os.Parcelable} types into
94     * the PendingIntent as extras, as the system will not be able to unparcel it to merge
95     * additional extras.</p>
96     *
97     * @param title title of this target that will be shown to a user
98     * @param icon icon to represent this target
99     * @param score ranking score for this target between 0.0f and 1.0f, inclusive
100     * @param pendingIntent PendingIntent to fill in and send if the user chooses this target
101     */
102    public ChooserTarget(CharSequence title, Bitmap icon, float score,
103            PendingIntent pendingIntent) {
104        this(title, icon, score, pendingIntent.getIntentSender());
105    }
106
107    /**
108     * Construct a deep link target for presentation by a chooser UI.
109     *
110     * <p>A target is composed of a title and an icon for presentation to the user.
111     * The UI presenting this target may truncate the title if it is too long to be presented
112     * in the available space, as well as crop, resize or overlay the supplied icon.</p>
113     *
114     * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
115     * to the other targets supplied by the same
116     * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
117     * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).
118     * Scores for a set of targets do not need to sum to 1.</p>
119     *
120     * <p>Before being sent, the IntentSender supplied will be
121     * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
122     * to the chooser. When constructing an IntentSender for use in a ChooserTarget, make sure
123     * that you permit the relevant fields to be filled in using the appropriate flags such as
124     * {@link Intent#FILL_IN_ACTION}, {@link Intent#FILL_IN_CATEGORIES},
125     * {@link Intent#FILL_IN_DATA} and {@link Intent#FILL_IN_CLIP_DATA}. Note that
126     * {@link Intent#FILL_IN_CLIP_DATA} is required to appropriately receive URI permission grants
127     * for {@link Intent#ACTION_SEND} intents.</p>
128     *
129     * <p>Take care not to place custom {@link android.os.Parcelable} types into
130     * the IntentSender as extras, as the system will not be able to unparcel it to merge
131     * additional extras.</p>
132     *
133     * @param title title of this target that will be shown to a user
134     * @param icon icon to represent this target
135     * @param score ranking score for this target between 0.0f and 1.0f, inclusive
136     * @param intentSender IntentSender to fill in and send if the user chooses this target
137     */
138    public ChooserTarget(CharSequence title, Bitmap icon, float score, IntentSender intentSender) {
139        mTitle = title;
140        mIcon = icon;
141        if (score > 1.f || score < 0.f) {
142            throw new IllegalArgumentException("Score " + score + " out of range; "
143                    + "must be between 0.0f and 1.0f");
144        }
145        mScore = score;
146        mIntentSender = intentSender;
147    }
148
149    /**
150     * Construct a deep link target for presentation by a chooser UI.
151     *
152     * <p>A target is composed of a title and an icon for presentation to the user.
153     * The UI presenting this target may truncate the title if it is too long to be presented
154     * in the available space, as well as crop, resize or overlay the supplied icon.</p>
155     *
156     * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
157     * to the other targets supplied by the same
158     * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
159     * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).
160     * Scores for a set of targets do not need to sum to 1.</p>
161     *
162     * <p>Before being sent, the Intent supplied will be
163     * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
164     * to the chooser.</p>
165     *
166     * <p>Take care not to place custom {@link android.os.Parcelable} types into
167     * the Intent as extras, as the system will not be able to unparcel it to merge
168     * additional extras.</p>
169     *
170     * @param title title of this target that will be shown to a user
171     * @param icon icon to represent this target
172     * @param score ranking score for this target between 0.0f and 1.0f, inclusive
173     * @param intent Intent to fill in and send if the user chooses this target
174     */
175    public ChooserTarget(CharSequence title, Bitmap icon, float score, Intent intent) {
176        mTitle = title;
177        mIcon = icon;
178        if (score > 1.f || score < 0.f) {
179            throw new IllegalArgumentException("Score " + score + " out of range; "
180                    + "must be between 0.0f and 1.0f");
181        }
182        mScore = score;
183        mIntent = intent;
184    }
185
186    ChooserTarget(Parcel in) {
187        mTitle = in.readCharSequence();
188        if (in.readInt() != 0) {
189            mIcon = Bitmap.CREATOR.createFromParcel(in);
190        } else {
191            mIcon = null;
192        }
193        mScore = in.readFloat();
194        mIntentSender = IntentSender.readIntentSenderOrNullFromParcel(in);
195        if (in.readInt() != 0) {
196            mIntent = Intent.CREATOR.createFromParcel(in);
197        }
198    }
199
200    /**
201     * Returns the title of this target for display to a user. The UI displaying the title
202     * may truncate this title if it is too long to be displayed in full.
203     *
204     * @return the title of this target, intended to be shown to a user
205     */
206    public CharSequence getTitle() {
207        return mTitle;
208    }
209
210    /**
211     * Returns the icon representing this target for display to a user. The UI displaying the icon
212     * may crop, resize or overlay this icon.
213     *
214     * @return the icon representing this target, intended to be shown to a user
215     */
216    public Bitmap getIcon() {
217        return mIcon;
218    }
219
220    /**
221     * Returns the ranking score supplied by the creator of this ChooserTarget.
222     * Values are between 0.0f and 1.0f. The UI displaying the target may
223     * take this score into account when sorting and merging targets from multiple sources.
224     *
225     * @return the ranking score for this target between 0.0f and 1.0f, inclusive
226     */
227    public float getScore() {
228        return mScore;
229    }
230
231    /**
232     * Returns the raw IntentSender supplied by the ChooserTarget's creator.
233     * This may be null if the creator specified a regular Intent instead.
234     *
235     * <p>To fill in and send the intent, see {@link #sendIntent(Context, Intent)}.</p>
236     *
237     * @return the IntentSender supplied by the ChooserTarget's creator
238     */
239    public IntentSender getIntentSender() {
240        return mIntentSender;
241    }
242
243    /**
244     * Returns the Intent supplied by the ChooserTarget's creator.
245     * This may be null if the creator specified an IntentSender or PendingIntent instead.
246     *
247     * <p>To fill in and send the intent, see {@link #sendIntent(Context, Intent)}.</p>
248     *
249     * @return the Intent supplied by the ChooserTarget's creator
250     */
251    public Intent getIntent() {
252        return mIntent;
253    }
254
255    /**
256     * Fill in the IntentSender supplied by the ChooserTarget's creator and send it.
257     *
258     * @param context the sending Context; generally the Activity presenting the chooser UI
259     * @param fillInIntent the Intent provided to the Chooser to be sent to a selected target
260     * @return true if sending the Intent was successful
261     */
262    public boolean sendIntent(Context context, Intent fillInIntent) {
263        if (fillInIntent != null) {
264            fillInIntent.migrateExtraStreamToClipData();
265            fillInIntent.prepareToLeaveProcess();
266        }
267        if (mIntentSender != null) {
268            try {
269                mIntentSender.sendIntent(context, 0, fillInIntent, null, null);
270                return true;
271            } catch (IntentSender.SendIntentException e) {
272                Log.e(TAG, "sendIntent " + this + " failed", e);
273                return false;
274            }
275        } else if (mIntent != null) {
276            try {
277                final Intent toSend = new Intent(mIntent);
278                toSend.fillIn(fillInIntent, 0);
279                context.startActivity(toSend);
280                return true;
281            } catch (Exception e) {
282                Log.e(TAG, "sendIntent " + this + " failed", e);
283                return false;
284            }
285        } else {
286            Log.e(TAG, "sendIntent " + this + " failed - no IntentSender or Intent to send");
287            return false;
288        }
289    }
290
291    /**
292     * Same as {@link #sendIntent(Context, Intent)}, but offers a userId field to use
293     * for launching the {@link #getIntent() intent} using
294     * {@link Activity#startActivityAsCaller(Intent, Bundle, int)} if the
295     * {@link #getIntentSender() IntentSender} is not present. If the IntentSender is present,
296     * it will be invoked as usual with its own calling identity.
297     *
298     * @hide internal use only.
299     */
300    public boolean sendIntentAsCaller(Activity context, Intent fillInIntent, int userId) {
301        if (fillInIntent != null) {
302            fillInIntent.migrateExtraStreamToClipData();
303            fillInIntent.prepareToLeaveProcess();
304        }
305        if (mIntentSender != null) {
306            try {
307                mIntentSender.sendIntent(context, 0, fillInIntent, null, null);
308                return true;
309            } catch (IntentSender.SendIntentException e) {
310                Log.e(TAG, "sendIntent " + this + " failed", e);
311                return false;
312            }
313        } else if (mIntent != null) {
314            try {
315                final Intent toSend = new Intent(mIntent);
316                toSend.fillIn(fillInIntent, 0);
317                context.startActivityAsCaller(toSend, null, userId);
318                return true;
319            } catch (Exception e) {
320                Log.e(TAG, "sendIntent " + this + " failed", e);
321                return false;
322            }
323        } else {
324            Log.e(TAG, "sendIntent " + this + " failed - no IntentSender or Intent to send");
325            return false;
326        }
327    }
328
329    /**
330     * The UserHandle is only used if we're launching a raw intent. The IntentSender will be
331     * launched with its associated identity.
332     *
333     * @hide Internal use only
334     */
335    public boolean sendIntentAsUser(Activity context, Intent fillInIntent, UserHandle user) {
336        if (fillInIntent != null) {
337            fillInIntent.migrateExtraStreamToClipData();
338            fillInIntent.prepareToLeaveProcess();
339        }
340        if (mIntentSender != null) {
341            try {
342                mIntentSender.sendIntent(context, 0, fillInIntent, null, null);
343                return true;
344            } catch (IntentSender.SendIntentException e) {
345                Log.e(TAG, "sendIntent " + this + " failed", e);
346                return false;
347            }
348        } else if (mIntent != null) {
349            try {
350                final Intent toSend = new Intent(mIntent);
351                toSend.fillIn(fillInIntent, 0);
352                context.startActivityAsUser(toSend, user);
353                return true;
354            } catch (Exception e) {
355                Log.e(TAG, "sendIntent " + this + " failed", e);
356                return false;
357            }
358        } else {
359            Log.e(TAG, "sendIntent " + this + " failed - no IntentSender or Intent to send");
360            return false;
361        }
362    }
363
364    @Override
365    public String toString() {
366        return "ChooserTarget{"
367                + (mIntentSender != null ? mIntentSender.getCreatorPackage() : mIntent)
368                + ", "
369                + "'" + mTitle
370                + "', " + mScore + "}";
371    }
372
373    @Override
374    public int describeContents() {
375        return 0;
376    }
377
378    @Override
379    public void writeToParcel(Parcel dest, int flags) {
380        dest.writeCharSequence(mTitle);
381        if (mIcon != null) {
382            dest.writeInt(1);
383            mIcon.writeToParcel(dest, 0);
384        } else {
385            dest.writeInt(0);
386        }
387        dest.writeFloat(mScore);
388        IntentSender.writeIntentSenderOrNullToParcel(mIntentSender, dest);
389        dest.writeInt(mIntent != null ? 1 : 0);
390        if (mIntent != null) {
391            mIntent.writeToParcel(dest, 0);
392        }
393    }
394
395    public static final Creator<ChooserTarget> CREATOR
396            = new Creator<ChooserTarget>() {
397        @Override
398        public ChooserTarget createFromParcel(Parcel source) {
399            return new ChooserTarget(source);
400        }
401
402        @Override
403        public ChooserTarget[] newArray(int size) {
404            return new ChooserTarget[size];
405        }
406    };
407}
408