1 /*
2  * Copyright (C) 2009 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.nfc;
18 
19 import org.xmlpull.v1.XmlPullParser;
20 import org.xmlpull.v1.XmlPullParserException;
21 
22 import android.app.ActivityManager;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.res.Resources;
32 import android.content.res.XmlResourceParser;
33 import android.os.UserHandle;
34 import android.util.Log;
35 
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.concurrent.atomic.AtomicReference;
40 
41 /**
42  * A cache of intent filters registered to receive the TECH_DISCOVERED dispatch.
43  */
44 public class RegisteredComponentCache {
45     private static final String TAG = "RegisteredComponentCache";
46     private static final boolean DEBUG = false;
47 
48     final Context mContext;
49     final String mAction;
50     final String mMetaDataName;
51     final AtomicReference<BroadcastReceiver> mReceiver;
52 
53     // synchronized on this
54     private ArrayList<ComponentInfo> mComponents;
55 
RegisteredComponentCache(Context context, String action, String metaDataName)56     public RegisteredComponentCache(Context context, String action, String metaDataName) {
57         mContext = context;
58         mAction = action;
59         mMetaDataName = metaDataName;
60 
61         generateComponentsList();
62 
63         final BroadcastReceiver receiver = new BroadcastReceiver() {
64             @Override
65             public void onReceive(Context context1, Intent intent) {
66                 generateComponentsList();
67             }
68         };
69         mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
70         IntentFilter intentFilter = new IntentFilter();
71         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
72         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
73         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
74         intentFilter.addDataScheme("package");
75         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, intentFilter, null, null);
76         // Register for events related to sdcard installation.
77         IntentFilter sdFilter = new IntentFilter();
78         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
79         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
80         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, sdFilter, null, null);
81         // Generate a new list upon switching users as well
82         IntentFilter userFilter = new IntentFilter();
83         userFilter.addAction(Intent.ACTION_USER_SWITCHED);
84         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, userFilter, null, null);
85     }
86 
87     public static class ComponentInfo {
88         public final ResolveInfo resolveInfo;
89         public final String[] techs;
90 
ComponentInfo(ResolveInfo resolveInfo, String[] techs)91         ComponentInfo(ResolveInfo resolveInfo, String[] techs) {
92             this.resolveInfo = resolveInfo;
93             this.techs = techs;
94         }
95 
96         @Override
toString()97         public String toString() {
98             StringBuilder out = new StringBuilder("ComponentInfo: ");
99             out.append(resolveInfo);
100             out.append(", techs: ");
101             for (String tech : techs) {
102                 out.append(tech);
103                 out.append(", ");
104             }
105             return out.toString();
106         }
107     }
108 
109     /**
110      * @return a collection of {@link RegisteredComponentCache.ComponentInfo} objects for all
111      * registered authenticators.
112      */
getComponents()113     public ArrayList<ComponentInfo> getComponents() {
114         synchronized (this) {
115             // It's safe to return a reference here since mComponents is always replaced and
116             // never updated when it changes.
117             return mComponents;
118         }
119     }
120 
121     /**
122      * Stops the monitoring of package additions, removals and changes.
123      */
close()124     public void close() {
125         final BroadcastReceiver receiver = mReceiver.getAndSet(null);
126         if (receiver != null) {
127             mContext.unregisterReceiver(receiver);
128         }
129     }
130 
131     @Override
finalize()132     protected void finalize() throws Throwable {
133         if (mReceiver.get() != null) {
134             Log.e(TAG, "RegisteredServicesCache finalized without being closed");
135         }
136         close();
137         super.finalize();
138     }
139 
dump(ArrayList<ComponentInfo> components)140     void dump(ArrayList<ComponentInfo> components) {
141         for (ComponentInfo component : components) {
142             Log.i(TAG, component.toString());
143         }
144     }
145 
generateComponentsList()146     void generateComponentsList() {
147         PackageManager pm;
148         try {
149             UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
150             pm = mContext.createPackageContextAsUser("android", 0,
151                     currentUser).getPackageManager();
152         } catch (NameNotFoundException e) {
153             Log.e(TAG, "Could not create user package context");
154             return;
155         }
156         ArrayList<ComponentInfo> components = new ArrayList<ComponentInfo>();
157         List<ResolveInfo> resolveInfos = pm.queryIntentActivitiesAsUser(new Intent(mAction),
158                 PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
159         for (ResolveInfo resolveInfo : resolveInfos) {
160             try {
161                 parseComponentInfo(pm, resolveInfo, components);
162             } catch (XmlPullParserException e) {
163                 Log.w(TAG, "Unable to load component info " + resolveInfo.toString(), e);
164             } catch (IOException e) {
165                 Log.w(TAG, "Unable to load component info " + resolveInfo.toString(), e);
166             }
167         }
168 
169         if (DEBUG) {
170             dump(components);
171         }
172 
173         synchronized (this) {
174             mComponents = components;
175         }
176     }
177 
parseComponentInfo(PackageManager pm, ResolveInfo info, ArrayList<ComponentInfo> components)178     void parseComponentInfo(PackageManager pm, ResolveInfo info,
179             ArrayList<ComponentInfo> components) throws XmlPullParserException, IOException {
180         ActivityInfo ai = info.activityInfo;
181 
182         XmlResourceParser parser = null;
183         try {
184             parser = ai.loadXmlMetaData(pm, mMetaDataName);
185             if (parser == null) {
186                 throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
187             }
188 
189             parseTechLists(pm.getResourcesForApplication(ai.applicationInfo), ai.packageName,
190                     parser, info, components);
191         } catch (NameNotFoundException e) {
192             throw new XmlPullParserException("Unable to load resources for " + ai.packageName);
193         } finally {
194             if (parser != null) parser.close();
195         }
196     }
197 
parseTechLists(Resources res, String packageName, XmlPullParser parser, ResolveInfo resolveInfo, ArrayList<ComponentInfo> components)198     void parseTechLists(Resources res, String packageName, XmlPullParser parser,
199             ResolveInfo resolveInfo, ArrayList<ComponentInfo> components)
200             throws XmlPullParserException, IOException {
201         int eventType = parser.getEventType();
202         while (eventType != XmlPullParser.START_TAG) {
203             eventType = parser.next();
204         }
205 
206         ArrayList<String> items = new ArrayList<String>();
207         String tagName;
208         eventType = parser.next();
209         do {
210             tagName = parser.getName();
211             if (eventType == XmlPullParser.START_TAG && "tech".equals(tagName)) {
212                 items.add(parser.nextText());
213             } else if (eventType == XmlPullParser.END_TAG && "tech-list".equals(tagName)) {
214                 int size = items.size();
215                 if (size > 0) {
216                     String[] techs = new String[size];
217                     techs = items.toArray(techs);
218                     items.clear();
219                     components.add(new ComponentInfo(resolveInfo, techs));
220                 }
221             }
222             eventType = parser.next();
223         } while (eventType != XmlPullParser.END_DOCUMENT);
224     }
225 }
226