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