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