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