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
17package com.android.nfc;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.app.ActivityManager;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.ResolveInfo;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.res.Resources;
32import android.content.res.XmlResourceParser;
33import android.os.UserHandle;
34import android.util.Log;
35
36import java.io.IOException;
37import java.util.ArrayList;
38import java.util.List;
39import java.util.concurrent.atomic.AtomicReference;
40
41/**
42 * A cache of intent filters registered to receive the TECH_DISCOVERED dispatch.
43 */
44public 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
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
91        ComponentInfo(ResolveInfo resolveInfo, String[] techs) {
92            this.resolveInfo = resolveInfo;
93            this.techs = techs;
94        }
95
96        @Override
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     */
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     */
124    public void close() {
125        final BroadcastReceiver receiver = mReceiver.getAndSet(null);
126        if (receiver != null) {
127            mContext.unregisterReceiver(receiver);
128        }
129    }
130
131    @Override
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
140    void dump(ArrayList<ComponentInfo> components) {
141        for (ComponentInfo component : components) {
142            Log.i(TAG, component.toString());
143        }
144    }
145
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
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
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