1 /*
2  * Copyright (C) 2011 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 com.android.server.pm;
18 
19 import android.content.ComponentName;
20 import android.content.IntentFilter;
21 import android.content.pm.ActivityInfo;
22 import android.content.pm.PackageManager;
23 import android.content.pm.PackageManagerInternal;
24 import android.content.pm.ResolveInfo;
25 import android.util.Slog;
26 
27 import com.android.internal.util.XmlUtils;
28 import com.android.modules.utils.TypedXmlPullParser;
29 import com.android.modules.utils.TypedXmlSerializer;
30 import com.android.server.LocalServices;
31 import com.android.server.pm.pkg.PackageUserState;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 
36 import java.io.IOException;
37 import java.io.PrintWriter;
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 public class PreferredComponent {
42     private static final String TAG_SET = "set";
43     private static final String ATTR_ALWAYS = "always"; // boolean
44     private static final String ATTR_MATCH = "match"; // number
45     private static final String ATTR_NAME = "name"; // component name
46     private static final String ATTR_SET = "set"; // number
47 
48     public final int mMatch;
49     public final ComponentName mComponent;
50     // Whether this is to be the one that's always chosen. If false, it's the most recently chosen.
51     public boolean mAlways;
52 
53     final String[] mSetPackages;
54     final String[] mSetClasses;
55     final String[] mSetComponents;
56     final String mShortComponent;
57     private String mParseError;
58 
59     private final Callbacks mCallbacks;
60 
61     public interface Callbacks {
onReadTag(String tagName, TypedXmlPullParser parser)62         public boolean onReadTag(String tagName, TypedXmlPullParser parser)
63                 throws XmlPullParserException, IOException;
64     }
65 
PreferredComponent(Callbacks callbacks, int match, ComponentName[] set, ComponentName component, boolean always)66     public PreferredComponent(Callbacks callbacks, int match, ComponentName[] set,
67             ComponentName component, boolean always) {
68         mCallbacks = callbacks;
69         mMatch = match&IntentFilter.MATCH_CATEGORY_MASK;
70         mComponent = component;
71         mAlways = always;
72         mShortComponent = component.flattenToShortString();
73         mParseError = null;
74         if (set != null) {
75             final int N = set.length;
76             String[] myPackages = new String[N];
77             String[] myClasses = new String[N];
78             String[] myComponents = new String[N];
79             for (int i=0; i<N; i++) {
80                 ComponentName cn = set[i];
81                 if (cn == null) {
82                     mSetPackages = null;
83                     mSetClasses = null;
84                     mSetComponents = null;
85                     return;
86                 }
87                 myPackages[i] = cn.getPackageName().intern();
88                 myClasses[i] = cn.getClassName().intern();
89                 myComponents[i] = cn.flattenToShortString();
90             }
91             mSetPackages = myPackages;
92             mSetClasses = myClasses;
93             mSetComponents = myComponents;
94         } else {
95             mSetPackages = null;
96             mSetClasses = null;
97             mSetComponents = null;
98         }
99     }
100 
PreferredComponent(Callbacks callbacks, TypedXmlPullParser parser)101     public PreferredComponent(Callbacks callbacks, TypedXmlPullParser parser)
102             throws XmlPullParserException, IOException {
103         mCallbacks = callbacks;
104         mShortComponent = parser.getAttributeValue(null, ATTR_NAME);
105         mComponent = ComponentName.unflattenFromString(mShortComponent);
106         if (mComponent == null) {
107             mParseError = "Bad activity name " + mShortComponent;
108         }
109         mMatch = parser.getAttributeIntHex(null, ATTR_MATCH, 0);
110         int setCount = parser.getAttributeInt(null, ATTR_SET, 0);
111         mAlways = parser.getAttributeBoolean(null, ATTR_ALWAYS, true);
112 
113         String[] myPackages = setCount > 0 ? new String[setCount] : null;
114         String[] myClasses = setCount > 0 ? new String[setCount] : null;
115         String[] myComponents = setCount > 0 ? new String[setCount] : null;
116 
117         int setPos = 0;
118 
119         int outerDepth = parser.getDepth();
120         int type;
121         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
122                && (type != XmlPullParser.END_TAG
123                        || parser.getDepth() > outerDepth)) {
124             if (type == XmlPullParser.END_TAG
125                     || type == XmlPullParser.TEXT) {
126                 continue;
127             }
128 
129             String tagName = parser.getName();
130             //Log.i(TAG, "Parse outerDepth=" + outerDepth + " depth="
131             //        + parser.getDepth() + " tag=" + tagName);
132             if (tagName.equals(TAG_SET)) {
133                 String name = parser.getAttributeValue(null, ATTR_NAME);
134                 if (name == null) {
135                     if (mParseError == null) {
136                         mParseError = "No name in set tag in preferred activity "
137                             + mShortComponent;
138                     }
139                 } else if (setPos >= setCount) {
140                     if (mParseError == null) {
141                         mParseError = "Too many set tags in preferred activity "
142                             + mShortComponent;
143                     }
144                 } else {
145                     ComponentName cn = ComponentName.unflattenFromString(name);
146                     if (cn == null) {
147                         if (mParseError == null) {
148                             mParseError = "Bad set name " + name + " in preferred activity "
149                                 + mShortComponent;
150                         }
151                     } else {
152                         myPackages[setPos] = cn.getPackageName();
153                         myClasses[setPos] = cn.getClassName();
154                         myComponents[setPos] = name;
155                         setPos++;
156                     }
157                 }
158                 XmlUtils.skipCurrentTag(parser);
159             } else if (!mCallbacks.onReadTag(tagName, parser)) {
160                 Slog.w("PreferredComponent", "Unknown element: " + parser.getName());
161                 XmlUtils.skipCurrentTag(parser);
162             }
163         }
164 
165         if (setPos != setCount) {
166             if (mParseError == null) {
167                 mParseError = "Not enough set tags (expected " + setCount
168                     + " but found " + setPos + ") in " + mShortComponent;
169             }
170         }
171 
172         mSetPackages = myPackages;
173         mSetClasses = myClasses;
174         mSetComponents = myComponents;
175     }
176 
getParseError()177     public String getParseError() {
178         return mParseError;
179     }
180 
writeToXml(TypedXmlSerializer serializer, boolean full)181     public void writeToXml(TypedXmlSerializer serializer, boolean full) throws IOException {
182         final int NS = mSetClasses != null ? mSetClasses.length : 0;
183         serializer.attribute(null, ATTR_NAME, mShortComponent);
184         if (full) {
185             if (mMatch != 0) {
186                 serializer.attributeIntHex(null, ATTR_MATCH, mMatch);
187             }
188             serializer.attributeBoolean(null, ATTR_ALWAYS, mAlways);
189             serializer.attributeInt(null, ATTR_SET, NS);
190             for (int s=0; s<NS; s++) {
191                 serializer.startTag(null, TAG_SET);
192                 serializer.attribute(null, ATTR_NAME, mSetComponents[s]);
193                 serializer.endTag(null, TAG_SET);
194             }
195         }
196     }
197 
sameSet(List<ResolveInfo> query, boolean excludeSetupWizardPackage, int userId)198     public boolean sameSet(List<ResolveInfo> query, boolean excludeSetupWizardPackage, int userId) {
199         if (mSetPackages == null) {
200             return query == null;
201         }
202         if (query == null) {
203             return false;
204         }
205         final int NQ = query.size();
206         final int NS = mSetPackages.length;
207         final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
208         String setupWizardPackageName = pmi.getSetupWizardPackageName();
209         int numMatch = 0;
210         for (int i=0; i<NQ; i++) {
211             ResolveInfo ri = query.get(i);
212             ActivityInfo ai = ri.activityInfo;
213             boolean good = false;
214 
215             // ignore SetupWizard package's launcher capability because it is only existed
216             // during SetupWizard is running
217             if (excludeSetupWizardPackage && ai.packageName.equals(setupWizardPackageName)) {
218                 continue;
219             }
220 
221             // Avoid showing the disambiguation dialog if the package which is installed with
222             // reason INSTALL_REASON_DEVICE_SETUP.
223             final PackageUserState pkgUserState =
224                     pmi.getPackageStateInternal(ai.packageName).getUserStates().get(userId);
225             if (pkgUserState != null && pkgUserState.getInstallReason()
226                     == PackageManager.INSTALL_REASON_DEVICE_SETUP) {
227                 continue;
228             }
229 
230             for (int j=0; j<NS; j++) {
231                 if (mSetPackages[j].equals(ai.packageName)
232                         && mSetClasses[j].equals(ai.name)) {
233                     numMatch++;
234                     good = true;
235                     break;
236                 }
237             }
238             if (!good) return false;
239         }
240         return numMatch == NS;
241     }
242 
sameSet(ComponentName[] comps)243     public boolean sameSet(ComponentName[] comps) {
244         if (mSetPackages == null) return false;
245         final int NQ = comps.length;
246         final int NS = mSetPackages.length;
247         int numMatch = 0;
248         for (int i=0; i<NQ; i++) {
249             ComponentName cn = comps[i];
250             boolean good = false;
251             for (int j=0; j<NS; j++) {
252                 if (mSetPackages[j].equals(cn.getPackageName())
253                         && mSetClasses[j].equals(cn.getClassName())) {
254                     numMatch++;
255                     good = true;
256                     break;
257                 }
258             }
259             if (!good) return false;
260         }
261         return numMatch == NS;
262     }
263 
sameSet(PreferredComponent pc)264     public boolean sameSet(PreferredComponent pc) {
265         if (mSetPackages == null || pc == null || pc.mSetPackages == null
266                 || !sameComponent(pc.mComponent)) {
267             return false;
268         }
269         final int otherPackageCount = pc.mSetPackages.length;
270         final int packageCount = mSetPackages.length;
271         if (otherPackageCount != packageCount) {
272             return false;
273         }
274         for (int i = 0; i < packageCount; i++) {
275             if (!mSetPackages[i].equals(pc.mSetPackages[i])
276                     || !mSetClasses[i].equals(pc.mSetClasses[i])) {
277                 return false;
278             }
279         }
280         return true;
281     }
282 
283     /** Returns true if the preferred component represents the provided ComponentName. */
sameComponent(ComponentName comp)284     private boolean sameComponent(ComponentName comp) {
285         if (mComponent == null || comp == null) {
286             return false;
287         }
288         if (mComponent.getPackageName().equals(comp.getPackageName())
289                 && mComponent.getClassName().equals(comp.getClassName())) {
290             return true;
291         }
292         return false;
293     }
294 
isSuperset(List<ResolveInfo> query, boolean excludeSetupWizardPackage)295     public boolean isSuperset(List<ResolveInfo> query, boolean excludeSetupWizardPackage) {
296         if (mSetPackages == null) {
297             return query == null;
298         }
299         if (query == null) {
300             return true;
301         }
302         final int NQ = query.size();
303         final int NS = mSetPackages.length;
304         if (!excludeSetupWizardPackage && NS < NQ) {
305             return false;
306         }
307         final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
308         String setupWizardPackageName = pmi.getSetupWizardPackageName();
309         for (int i=0; i<NQ; i++) {
310             ResolveInfo ri = query.get(i);
311             ActivityInfo ai = ri.activityInfo;
312             boolean foundMatch = false;
313 
314             // ignore SetupWizard package's launcher capability because it is only existed
315             // during SetupWizard is running
316             if (excludeSetupWizardPackage && ai.packageName.equals(setupWizardPackageName)) {
317                 continue;
318             }
319 
320             for (int j=0; j<NS; j++) {
321                 if (mSetPackages[j].equals(ai.packageName) && mSetClasses[j].equals(ai.name)) {
322                     foundMatch = true;
323                     break;
324                 }
325             }
326             if (!foundMatch) return false;
327         }
328         return true;
329     }
330 
331     /** Returns components from mSetPackages that are present in query. */
discardObsoleteComponents(List<ResolveInfo> query)332     public ComponentName[] discardObsoleteComponents(List<ResolveInfo> query) {
333         if (mSetPackages == null || query == null) {
334             return new ComponentName[0];
335         }
336         final int NQ = query.size();
337         final int NS = mSetPackages.length;
338         ArrayList<ComponentName> aliveComponents = new ArrayList<>();
339         for (int i = 0; i < NQ; i++) {
340             ResolveInfo ri = query.get(i);
341             ActivityInfo ai = ri.activityInfo;
342             for (int j = 0; j < NS; j++) {
343                 if (mSetPackages[j].equals(ai.packageName) && mSetClasses[j].equals(ai.name)) {
344                     aliveComponents.add(new ComponentName(mSetPackages[j], mSetClasses[j]));
345                     break;
346                 }
347             }
348         }
349         return aliveComponents.toArray(new ComponentName[aliveComponents.size()]);
350     }
351 
dump(PrintWriter out, String prefix, Object ident)352     public void dump(PrintWriter out, String prefix, Object ident) {
353         out.print(prefix); out.print(
354                 Integer.toHexString(System.identityHashCode(ident)));
355                 out.print(' ');
356                 out.println(mShortComponent);
357         out.print(prefix); out.print(" mMatch=0x");
358                 out.print(Integer.toHexString(mMatch));
359                 out.print(" mAlways="); out.println(mAlways);
360         if (mSetComponents != null) {
361             out.print(prefix); out.println("  Selected from:");
362             for (int i=0; i<mSetComponents.length; i++) {
363                 out.print(prefix); out.print("    ");
364                         out.println(mSetComponents[i]);
365             }
366         }
367     }
368 }
369