1/*
2 * Copyright (C) 2007 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.content.pm;
18
19import android.content.ComponentName;
20import android.content.IntentFilter;
21import android.graphics.drawable.Drawable;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.os.UserHandle;
25import android.text.TextUtils;
26import android.util.Printer;
27import android.util.Slog;
28
29import java.text.Collator;
30import java.util.Comparator;
31
32/**
33 * Information that is returned from resolving an intent
34 * against an IntentFilter. This partially corresponds to
35 * information collected from the AndroidManifest.xml's
36 * <intent> tags.
37 */
38public class ResolveInfo implements Parcelable {
39    private static final String TAG = "ResolveInfo";
40
41    /**
42     * The activity or broadcast receiver that corresponds to this resolution
43     * match, if this resolution is for an activity or broadcast receiver.
44     * Exactly one of {@link #activityInfo}, {@link #serviceInfo}, or
45     * {@link #providerInfo} will be non-null.
46     */
47    public ActivityInfo activityInfo;
48
49    /**
50     * The service that corresponds to this resolution match, if this resolution
51     * is for a service. Exactly one of {@link #activityInfo},
52     * {@link #serviceInfo}, or {@link #providerInfo} will be non-null.
53     */
54    public ServiceInfo serviceInfo;
55
56    /**
57     * The provider that corresponds to this resolution match, if this
58     * resolution is for a provider. Exactly one of {@link #activityInfo},
59     * {@link #serviceInfo}, or {@link #providerInfo} will be non-null.
60     */
61    public ProviderInfo providerInfo;
62
63    /**
64     * The ephemeral application that corresponds to this resolution match. This will
65     * only be set in specific circumstances.
66     * @hide
67     */
68    public EphemeralResolveInfo ephemeralResolveInfo;
69
70    /**
71     * A ResolveInfo that points at the ephemeral installer.
72     * @hide
73     */
74    public ResolveInfo ephemeralInstaller;
75
76    /**
77     * The IntentFilter that was matched for this ResolveInfo.
78     */
79    public IntentFilter filter;
80
81    /**
82     * The declared priority of this match.  Comes from the "priority"
83     * attribute or, if not set, defaults to 0.  Higher values are a higher
84     * priority.
85     */
86    public int priority;
87
88    /**
89     * Order of result according to the user's preference.  If the user
90     * has not set a preference for this result, the value is 0; higher
91     * values are a higher priority.
92     */
93    public int preferredOrder;
94
95    /**
96     * The system's evaluation of how well the activity matches the
97     * IntentFilter.  This is a match constant, a combination of
98     * {@link IntentFilter#MATCH_CATEGORY_MASK IntentFilter.MATCH_CATEGORY_MASK}
99     * and {@link IntentFilter#MATCH_ADJUSTMENT_MASK IntentFiler.MATCH_ADJUSTMENT_MASK}.
100     */
101    public int match;
102
103    /**
104     * Only set when returned by
105     * {@link PackageManager#queryIntentActivityOptions}, this tells you
106     * which of the given specific intents this result came from.  0 is the
107     * first in the list, < 0 means it came from the generic Intent query.
108     */
109    public int specificIndex = -1;
110
111    /**
112     * This filter has specified the Intent.CATEGORY_DEFAULT, meaning it
113     * would like to be considered a default action that the user can
114     * perform on this data.
115     */
116    public boolean isDefault;
117
118    /**
119     * A string resource identifier (in the package's resources) of this
120     * match's label.  From the "label" attribute or, if not set, 0.
121     */
122    public int labelRes;
123
124    /**
125     * The actual string retrieve from <var>labelRes</var> or null if none
126     * was provided.
127     */
128    public CharSequence nonLocalizedLabel;
129
130    /**
131     * A drawable resource identifier (in the package's resources) of this
132     * match's icon.  From the "icon" attribute or, if not set, 0. It is
133     * set only if the icon can be obtained by resource id alone.
134     */
135    public int icon;
136
137    /**
138     * Optional -- if non-null, the {@link #labelRes} and {@link #icon}
139     * resources will be loaded from this package, rather than the one
140     * containing the resolved component.
141     */
142    public String resolvePackageName;
143
144    /**
145     * If not equal to UserHandle.USER_CURRENT, then the intent will be forwarded to this user.
146     * @hide
147     */
148    public int targetUserId;
149
150    /**
151     * Set to true if the icon cannot be obtained by resource ids alone.
152     * It is set to true for ResolveInfos from the managed profile: They need to
153     * have their icon badged, so it cannot be obtained by resource ids alone.
154     * @hide
155     */
156    public boolean noResourceId;
157
158    /**
159     * Same as {@link #icon} but it will always correspond to "icon" attribute
160     * regardless of {@link #noResourceId} value.
161     * @hide
162     */
163    public int iconResourceId;
164
165    /**
166     * @hide Target comes from system process?
167     */
168    public boolean system;
169
170    /**
171     * @hide Does the associated IntentFilter comes from a Browser ?
172     */
173    public boolean handleAllWebDataURI;
174
175    /** {@hide} */
176    public ComponentInfo getComponentInfo() {
177        if (activityInfo != null) return activityInfo;
178        if (serviceInfo != null) return serviceInfo;
179        if (providerInfo != null) return providerInfo;
180        throw new IllegalStateException("Missing ComponentInfo!");
181    }
182
183    /**
184     * Retrieve the current textual label associated with this resolution.  This
185     * will call back on the given PackageManager to load the label from
186     * the application.
187     *
188     * @param pm A PackageManager from which the label can be loaded; usually
189     * the PackageManager from which you originally retrieved this item.
190     *
191     * @return Returns a CharSequence containing the resolutions's label.  If the
192     * item does not have a label, its name is returned.
193     */
194    public CharSequence loadLabel(PackageManager pm) {
195        if (nonLocalizedLabel != null) {
196            return nonLocalizedLabel;
197        }
198        CharSequence label;
199        if (resolvePackageName != null && labelRes != 0) {
200            label = pm.getText(resolvePackageName, labelRes, null);
201            if (label != null) {
202                return label.toString().trim();
203            }
204        }
205        ComponentInfo ci = getComponentInfo();
206        ApplicationInfo ai = ci.applicationInfo;
207        if (labelRes != 0) {
208            label = pm.getText(ci.packageName, labelRes, ai);
209            if (label != null) {
210                return label.toString().trim();
211            }
212        }
213
214        CharSequence data = ci.loadLabel(pm);
215        // Make the data safe
216        if (data != null) data = data.toString().trim();
217        return data;
218    }
219
220    /**
221     * Retrieve the current graphical icon associated with this resolution.  This
222     * will call back on the given PackageManager to load the icon from
223     * the application.
224     *
225     * @param pm A PackageManager from which the icon can be loaded; usually
226     * the PackageManager from which you originally retrieved this item.
227     *
228     * @return Returns a Drawable containing the resolution's icon.  If the
229     * item does not have an icon, the default activity icon is returned.
230     */
231    public Drawable loadIcon(PackageManager pm) {
232        Drawable dr = null;
233        if (resolvePackageName != null && iconResourceId != 0) {
234            dr = pm.getDrawable(resolvePackageName, iconResourceId, null);
235        }
236        ComponentInfo ci = getComponentInfo();
237        if (dr == null && iconResourceId != 0) {
238            ApplicationInfo ai = ci.applicationInfo;
239            dr = pm.getDrawable(ci.packageName, iconResourceId, ai);
240        }
241        if (dr != null) {
242            return pm.getUserBadgedIcon(dr, new UserHandle(UserHandle.myUserId()));
243        }
244        return ci.loadIcon(pm);
245    }
246
247    /**
248     * Return the icon resource identifier to use for this match.  If the
249     * match defines an icon, that is used; else if the activity defines
250     * an icon, that is used; else, the application icon is used.
251     * This function does not check noResourceId flag.
252     *
253     * @return The icon associated with this match.
254     */
255    final int getIconResourceInternal() {
256        if (iconResourceId != 0) return iconResourceId;
257        final ComponentInfo ci = getComponentInfo();
258        if (ci != null) {
259            return ci.getIconResource();
260        }
261        return 0;
262    }
263
264    /**
265     * Return the icon resource identifier to use for this match.  If the
266     * match defines an icon, that is used; else if the activity defines
267     * an icon, that is used; else, the application icon is used.
268     *
269     * @return The icon associated with this match.
270     */
271    public final int getIconResource() {
272        if (noResourceId) return 0;
273        return getIconResourceInternal();
274    }
275
276    public void dump(Printer pw, String prefix) {
277        dump(pw, prefix, PackageItemInfo.DUMP_FLAG_ALL);
278    }
279
280    /** @hide */
281    public void dump(Printer pw, String prefix, int flags) {
282        if (filter != null) {
283            pw.println(prefix + "Filter:");
284            filter.dump(pw, prefix + "  ");
285        }
286        pw.println(prefix + "priority=" + priority
287                + " preferredOrder=" + preferredOrder
288                + " match=0x" + Integer.toHexString(match)
289                + " specificIndex=" + specificIndex
290                + " isDefault=" + isDefault);
291        if (resolvePackageName != null) {
292            pw.println(prefix + "resolvePackageName=" + resolvePackageName);
293        }
294        if (labelRes != 0 || nonLocalizedLabel != null || icon != 0) {
295            pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
296                    + " nonLocalizedLabel=" + nonLocalizedLabel
297                    + " icon=0x" + Integer.toHexString(icon));
298        }
299        if (activityInfo != null) {
300            pw.println(prefix + "ActivityInfo:");
301            activityInfo.dump(pw, prefix + "  ", flags);
302        } else if (serviceInfo != null) {
303            pw.println(prefix + "ServiceInfo:");
304            serviceInfo.dump(pw, prefix + "  ", flags);
305        } else if (providerInfo != null) {
306            pw.println(prefix + "ProviderInfo:");
307            providerInfo.dump(pw, prefix + "  ", flags);
308        }
309    }
310
311    public ResolveInfo() {
312        targetUserId = UserHandle.USER_CURRENT;
313    }
314
315    public ResolveInfo(ResolveInfo orig) {
316        activityInfo = orig.activityInfo;
317        serviceInfo = orig.serviceInfo;
318        providerInfo = orig.providerInfo;
319        filter = orig.filter;
320        priority = orig.priority;
321        preferredOrder = orig.preferredOrder;
322        match = orig.match;
323        specificIndex = orig.specificIndex;
324        labelRes = orig.labelRes;
325        nonLocalizedLabel = orig.nonLocalizedLabel;
326        icon = orig.icon;
327        resolvePackageName = orig.resolvePackageName;
328        noResourceId = orig.noResourceId;
329        iconResourceId = orig.iconResourceId;
330        system = orig.system;
331        targetUserId = orig.targetUserId;
332        handleAllWebDataURI = orig.handleAllWebDataURI;
333    }
334
335    public String toString() {
336        final ComponentInfo ci = getComponentInfo();
337        StringBuilder sb = new StringBuilder(128);
338        sb.append("ResolveInfo{");
339        sb.append(Integer.toHexString(System.identityHashCode(this)));
340        sb.append(' ');
341        ComponentName.appendShortString(sb, ci.packageName, ci.name);
342        if (priority != 0) {
343            sb.append(" p=");
344            sb.append(priority);
345        }
346        if (preferredOrder != 0) {
347            sb.append(" o=");
348            sb.append(preferredOrder);
349        }
350        sb.append(" m=0x");
351        sb.append(Integer.toHexString(match));
352        if (targetUserId != UserHandle.USER_CURRENT) {
353            sb.append(" targetUserId=");
354            sb.append(targetUserId);
355        }
356        sb.append('}');
357        return sb.toString();
358    }
359
360    public int describeContents() {
361        return 0;
362    }
363
364    public void writeToParcel(Parcel dest, int parcelableFlags) {
365        if (activityInfo != null) {
366            dest.writeInt(1);
367            activityInfo.writeToParcel(dest, parcelableFlags);
368        } else if (serviceInfo != null) {
369            dest.writeInt(2);
370            serviceInfo.writeToParcel(dest, parcelableFlags);
371        } else if (providerInfo != null) {
372            dest.writeInt(3);
373            providerInfo.writeToParcel(dest, parcelableFlags);
374        } else {
375            dest.writeInt(0);
376        }
377        if (filter != null) {
378            dest.writeInt(1);
379            filter.writeToParcel(dest, parcelableFlags);
380        } else {
381            dest.writeInt(0);
382        }
383        dest.writeInt(priority);
384        dest.writeInt(preferredOrder);
385        dest.writeInt(match);
386        dest.writeInt(specificIndex);
387        dest.writeInt(labelRes);
388        TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
389        dest.writeInt(icon);
390        dest.writeString(resolvePackageName);
391        dest.writeInt(targetUserId);
392        dest.writeInt(system ? 1 : 0);
393        dest.writeInt(noResourceId ? 1 : 0);
394        dest.writeInt(iconResourceId);
395        dest.writeInt(handleAllWebDataURI ? 1 : 0);
396    }
397
398    public static final Creator<ResolveInfo> CREATOR
399            = new Creator<ResolveInfo>() {
400        public ResolveInfo createFromParcel(Parcel source) {
401            return new ResolveInfo(source);
402        }
403        public ResolveInfo[] newArray(int size) {
404            return new ResolveInfo[size];
405        }
406    };
407
408    private ResolveInfo(Parcel source) {
409        activityInfo = null;
410        serviceInfo = null;
411        providerInfo = null;
412        switch (source.readInt()) {
413            case 1:
414                activityInfo = ActivityInfo.CREATOR.createFromParcel(source);
415                break;
416            case 2:
417                serviceInfo = ServiceInfo.CREATOR.createFromParcel(source);
418                break;
419            case 3:
420                providerInfo = ProviderInfo.CREATOR.createFromParcel(source);
421                break;
422            default:
423                Slog.w(TAG, "Missing ComponentInfo!");
424                break;
425        }
426        if (source.readInt() != 0) {
427            filter = IntentFilter.CREATOR.createFromParcel(source);
428        }
429        priority = source.readInt();
430        preferredOrder = source.readInt();
431        match = source.readInt();
432        specificIndex = source.readInt();
433        labelRes = source.readInt();
434        nonLocalizedLabel
435                = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
436        icon = source.readInt();
437        resolvePackageName = source.readString();
438        targetUserId = source.readInt();
439        system = source.readInt() != 0;
440        noResourceId = source.readInt() != 0;
441        iconResourceId = source.readInt();
442        handleAllWebDataURI = source.readInt() != 0;
443    }
444
445    public static class DisplayNameComparator
446            implements Comparator<ResolveInfo> {
447        public DisplayNameComparator(PackageManager pm) {
448            mPM = pm;
449            mCollator.setStrength(Collator.PRIMARY);
450        }
451
452        public final int compare(ResolveInfo a, ResolveInfo b) {
453            // We want to put the one targeted to another user at the end of the dialog.
454            if (a.targetUserId != UserHandle.USER_CURRENT) {
455                return 1;
456            }
457            if (b.targetUserId != UserHandle.USER_CURRENT) {
458                return -1;
459            }
460            CharSequence  sa = a.loadLabel(mPM);
461            if (sa == null) sa = a.activityInfo.name;
462            CharSequence  sb = b.loadLabel(mPM);
463            if (sb == null) sb = b.activityInfo.name;
464
465            return mCollator.compare(sa.toString(), sb.toString());
466        }
467
468        private final Collator   mCollator = Collator.getInstance();
469        private PackageManager   mPM;
470    }
471}
472