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
17package com.android.nfc;
18
19import android.bluetooth.BluetoothAdapter;
20
21import com.android.nfc.RegisteredComponentCache.ComponentInfo;
22import com.android.nfc.handover.HandoverDataParser;
23import com.android.nfc.handover.PeripheralHandoverService;
24
25import android.app.Activity;
26import android.app.ActivityManager;
27import android.app.ActivityManagerNative;
28import android.app.IActivityManager;
29import android.app.PendingIntent;
30import android.app.PendingIntent.CanceledException;
31import android.content.ComponentName;
32import android.content.ContentResolver;
33import android.content.Context;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.pm.PackageManager;
37import android.content.pm.PackageManager.NameNotFoundException;
38import android.content.pm.ResolveInfo;
39import android.content.res.Resources.NotFoundException;
40import android.net.Uri;
41import android.nfc.NdefMessage;
42import android.nfc.NdefRecord;
43import android.nfc.NfcAdapter;
44import android.nfc.Tag;
45import android.nfc.tech.Ndef;
46import android.nfc.tech.NfcBarcode;
47import android.os.RemoteException;
48import android.os.UserHandle;
49import android.util.Log;
50
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
53import java.nio.charset.StandardCharsets;
54import java.util.ArrayList;
55import java.util.Arrays;
56import java.util.LinkedList;
57import java.util.List;
58
59/**
60 * Dispatch of NFC events to start activities
61 */
62class NfcDispatcher {
63    private static final boolean DBG = false;
64    private static final String TAG = "NfcDispatcher";
65
66    static final int DISPATCH_SUCCESS = 1;
67    static final int DISPATCH_FAIL = 2;
68    static final int DISPATCH_UNLOCK = 3;
69
70    private final Context mContext;
71    private final IActivityManager mIActivityManager;
72    private final RegisteredComponentCache mTechListFilters;
73    private final ContentResolver mContentResolver;
74    private final HandoverDataParser mHandoverDataParser;
75    private final String[] mProvisioningMimes;
76    private final ScreenStateHelper mScreenStateHelper;
77    private final NfcUnlockManager mNfcUnlockManager;
78    private final boolean mDeviceSupportsBluetooth;
79
80    // Locked on this
81    private PendingIntent mOverrideIntent;
82    private IntentFilter[] mOverrideFilters;
83    private String[][] mOverrideTechLists;
84    private boolean mProvisioningOnly;
85
86    NfcDispatcher(Context context,
87                  HandoverDataParser handoverDataParser,
88                  boolean provisionOnly) {
89        mContext = context;
90        mIActivityManager = ActivityManagerNative.getDefault();
91        mTechListFilters = new RegisteredComponentCache(mContext,
92                NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED);
93        mContentResolver = context.getContentResolver();
94        mHandoverDataParser = handoverDataParser;
95        mScreenStateHelper = new ScreenStateHelper(context);
96        mNfcUnlockManager = NfcUnlockManager.getInstance();
97        mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null;
98
99        synchronized (this) {
100            mProvisioningOnly = provisionOnly;
101        }
102        String[] provisionMimes = null;
103        if (provisionOnly) {
104            try {
105                // Get accepted mime-types
106                provisionMimes = context.getResources().
107                        getStringArray(R.array.provisioning_mime_types);
108            } catch (NotFoundException e) {
109               provisionMimes = null;
110            }
111        }
112        mProvisioningMimes = provisionMimes;
113    }
114
115    public synchronized void setForegroundDispatch(PendingIntent intent,
116            IntentFilter[] filters, String[][] techLists) {
117        if (DBG) Log.d(TAG, "Set Foreground Dispatch");
118        mOverrideIntent = intent;
119        mOverrideFilters = filters;
120        mOverrideTechLists = techLists;
121    }
122
123    public synchronized void disableProvisioningMode() {
124       mProvisioningOnly = false;
125    }
126
127    /**
128     * Helper for re-used objects and methods during a single tag dispatch.
129     */
130    static class DispatchInfo {
131        public final Intent intent;
132
133        final Intent rootIntent;
134        final Uri ndefUri;
135        final String ndefMimeType;
136        final PackageManager packageManager;
137        final Context context;
138
139        public DispatchInfo(Context context, Tag tag, NdefMessage message) {
140            intent = new Intent();
141            intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
142            intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
143            if (message != null) {
144                intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message});
145                ndefUri = message.getRecords()[0].toUri();
146                ndefMimeType = message.getRecords()[0].toMimeType();
147            } else {
148                ndefUri = null;
149                ndefMimeType = null;
150            }
151
152            rootIntent = new Intent(context, NfcRootActivity.class);
153            rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
154            rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
155
156            this.context = context;
157            packageManager = context.getPackageManager();
158        }
159
160        public Intent setNdefIntent() {
161            intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
162            if (ndefUri != null) {
163                intent.setData(ndefUri);
164                return intent;
165            } else if (ndefMimeType != null) {
166                intent.setType(ndefMimeType);
167                return intent;
168            }
169            return null;
170        }
171
172        public Intent setTechIntent() {
173            intent.setData(null);
174            intent.setType(null);
175            intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
176            return intent;
177        }
178
179        public Intent setTagIntent() {
180            intent.setData(null);
181            intent.setType(null);
182            intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED);
183            return intent;
184        }
185
186        /**
187         * Launch the activity via a (single) NFC root task, so that it
188         * creates a new task stack instead of interfering with any existing
189         * task stack for that activity.
190         * NfcRootActivity acts as the task root, it immediately calls
191         * start activity on the intent it is passed.
192         */
193        boolean tryStartActivity() {
194            // Ideally we'd have used startActivityForResult() to determine whether the
195            // NfcRootActivity was able to launch the intent, but startActivityForResult()
196            // is not available on Context. Instead, we query the PackageManager beforehand
197            // to determine if there is an Activity to handle this intent, and base the
198            // result of off that.
199            List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent, 0,
200                    ActivityManager.getCurrentUser());
201            if (activities.size() > 0) {
202                context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
203                return true;
204            }
205            return false;
206        }
207
208        boolean tryStartActivity(Intent intentToStart) {
209            List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(
210                    intentToStart, 0, ActivityManager.getCurrentUser());
211            if (activities.size() > 0) {
212                rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart);
213                context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
214                return true;
215            }
216            return false;
217        }
218    }
219
220    /** Returns:
221     * <ul>
222     *  <li /> DISPATCH_SUCCESS if dispatched to an activity,
223     *  <li /> DISPATCH_FAIL if no activities were found to dispatch to,
224     *  <li /> DISPATCH_UNLOCK if the tag was used to unlock the device
225     * </ul>
226     */
227    public int dispatchTag(Tag tag) {
228        PendingIntent overrideIntent;
229        IntentFilter[] overrideFilters;
230        String[][] overrideTechLists;
231        boolean provisioningOnly;
232
233        synchronized (this) {
234            overrideFilters = mOverrideFilters;
235            overrideIntent = mOverrideIntent;
236            overrideTechLists = mOverrideTechLists;
237            provisioningOnly = mProvisioningOnly;
238        }
239
240        boolean screenUnlocked = false;
241        if (!provisioningOnly &&
242                mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
243            screenUnlocked = handleNfcUnlock(tag);
244            if (!screenUnlocked) {
245                return DISPATCH_FAIL;
246            }
247        }
248
249        NdefMessage message = null;
250        Ndef ndef = Ndef.get(tag);
251        if (ndef != null) {
252            message = ndef.getCachedNdefMessage();
253        } else {
254            NfcBarcode nfcBarcode = NfcBarcode.get(tag);
255            if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) {
256                message = decodeNfcBarcodeUri(nfcBarcode);
257            }
258        }
259
260        if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
261
262        DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
263
264        resumeAppSwitches();
265
266        if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters,
267                overrideTechLists)) {
268            return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
269        }
270
271        if (tryPeripheralHandover(message)) {
272            if (DBG) Log.i(TAG, "matched BT HANDOVER");
273            return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
274        }
275
276        if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) {
277            if (DBG) Log.i(TAG, "matched NFC WPS TOKEN");
278            return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
279        }
280
281        if (tryNdef(dispatch, message, provisioningOnly)) {
282            return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
283        }
284
285        if (screenUnlocked) {
286            // We only allow NDEF-based mimeType matching in case of an unlock
287            return DISPATCH_UNLOCK;
288        }
289
290        if (provisioningOnly) {
291            // We only allow NDEF-based mimeType matching
292            return DISPATCH_FAIL;
293        }
294
295        // Only allow NDEF-based mimeType matching for unlock tags
296        if (tryTech(dispatch, tag)) {
297            return DISPATCH_SUCCESS;
298        }
299
300        dispatch.setTagIntent();
301        if (dispatch.tryStartActivity()) {
302            if (DBG) Log.i(TAG, "matched TAG");
303            return DISPATCH_SUCCESS;
304        }
305
306        if (DBG) Log.i(TAG, "no match");
307        return DISPATCH_FAIL;
308    }
309
310    private boolean handleNfcUnlock(Tag tag) {
311        return mNfcUnlockManager.tryUnlock(tag);
312    }
313
314    /**
315     * Checks for the presence of a URL stored in a tag with tech NfcBarcode.
316     * If found, decodes URL and returns NdefMessage message containing an
317     * NdefRecord containing the decoded URL. If not found, returns null.
318     *
319     * URLs are decoded as follows:
320     *
321     * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding
322     * to ISO/IEC 7816-6).
323     * The second byte describes the payload data format. There are four defined data
324     * format values that identify URL data. Depending on the data format value, the
325     * associated prefix is appended to the URL data:
326     *
327     * 0x01: URL with "http://www." prefix
328     * 0x02: URL with "https://www." prefix
329     * 0x03: URL with "http://" prefix
330     * 0x04: URL with "https://" prefix
331     *
332     * Other data format values do not identify URL data and are not handled by this function.
333     * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987.
334     * see http://www.ietf.org/rfc/rfc3987.txt
335     *
336     * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data,
337     * and are therefore not part of the payload. They are ignored in the decoding of a URL.
338     *
339     * The default assumption is that the URL occupies the entire payload of the NfcBarcode
340     * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes)
341     * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal
342     * an early end of the URL. Once this function reaches an early terminator byte 0xfe,
343     * URL decoding stops and the NdefMessage is created and returned. Any payload data after
344     * the first early terminator byte is ignored for the purposes of URL decoding.
345     */
346    private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) {
347        final byte URI_PREFIX_HTTP_WWW  = (byte) 0x01; // "http://www."
348        final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www."
349        final byte URI_PREFIX_HTTP      = (byte) 0x03; // "http://"
350        final byte URI_PREFIX_HTTPS     = (byte) 0x04; // "https://"
351
352        NdefMessage message = null;
353        byte[] tagId = nfcBarcode.getTag().getId();
354        // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes
355        if (tagId.length >= 4
356                && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW
357                    || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) {
358            // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to
359            // the end of the full NfcBarcode payload. No terminator means that the URI occupies the
360            // entire length of the payload field. Exclude checking the CRC in the final two bytes
361            // of the NfcBarcode tagId.
362            int end = 2;
363            for (; end < tagId.length - 2; end++) {
364                if (tagId[end] == (byte) 0xfe) {
365                    break;
366                }
367            }
368            byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID)
369            System.arraycopy(tagId, 1, payload, 0, payload.length);
370            NdefRecord uriRecord = new NdefRecord(
371                    NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload);
372            message = new NdefMessage(uriRecord);
373        }
374        return message;
375    }
376
377    boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,
378            IntentFilter[] overrideFilters, String[][] overrideTechLists) {
379        if (overrideIntent == null) {
380            return false;
381        }
382        Intent intent;
383
384        // NDEF
385        if (message != null) {
386            intent = dispatch.setNdefIntent();
387            if (intent != null &&
388                    isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
389                try {
390                    overrideIntent.send(mContext, Activity.RESULT_OK, intent);
391                    if (DBG) Log.i(TAG, "matched NDEF override");
392                    return true;
393                } catch (CanceledException e) {
394                    return false;
395                }
396            }
397        }
398
399        // TECH
400        intent = dispatch.setTechIntent();
401        if (isTechMatch(tag, overrideTechLists)) {
402            try {
403                overrideIntent.send(mContext, Activity.RESULT_OK, intent);
404                if (DBG) Log.i(TAG, "matched TECH override");
405                return true;
406            } catch (CanceledException e) {
407                return false;
408            }
409        }
410
411        // TAG
412        intent = dispatch.setTagIntent();
413        if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
414            try {
415                overrideIntent.send(mContext, Activity.RESULT_OK, intent);
416                if (DBG) Log.i(TAG, "matched TAG override");
417                return true;
418            } catch (CanceledException e) {
419                return false;
420            }
421        }
422        return false;
423    }
424
425    boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {
426        if (filters != null) {
427            for (IntentFilter filter : filters) {
428                if (filter.match(mContentResolver, intent, false, TAG) >= 0) {
429                    return true;
430                }
431            }
432        } else if (!hasTechFilter) {
433            return true;  // always match if both filters and techlists are null
434        }
435        return false;
436    }
437
438    boolean isTechMatch(Tag tag, String[][] techLists) {
439        if (techLists == null) {
440            return false;
441        }
442
443        String[] tagTechs = tag.getTechList();
444        Arrays.sort(tagTechs);
445        for (String[] filterTechs : techLists) {
446            if (filterMatch(tagTechs, filterTechs)) {
447                return true;
448            }
449        }
450        return false;
451    }
452
453    boolean tryNdef(DispatchInfo dispatch, NdefMessage message, boolean provisioningOnly) {
454        if (message == null) {
455            return false;
456        }
457        Intent intent = dispatch.setNdefIntent();
458
459        // Bail out if the intent does not contain filterable NDEF data
460        if (intent == null) return false;
461
462        if (provisioningOnly) {
463            if (mProvisioningMimes == null ||
464                    !(Arrays.asList(mProvisioningMimes).contains(intent.getType()))) {
465                Log.e(TAG, "Dropping NFC intent in provisioning mode.");
466                return false;
467            }
468        }
469
470        // Try to start AAR activity with matching filter
471        List<String> aarPackages = extractAarPackages(message);
472        for (String pkg : aarPackages) {
473            dispatch.intent.setPackage(pkg);
474            if (dispatch.tryStartActivity()) {
475                if (DBG) Log.i(TAG, "matched AAR to NDEF");
476                return true;
477            }
478        }
479
480        // Try to perform regular launch of the first AAR
481        if (aarPackages.size() > 0) {
482            String firstPackage = aarPackages.get(0);
483            PackageManager pm;
484            try {
485                UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
486                pm = mContext.createPackageContextAsUser("android", 0,
487                        currentUser).getPackageManager();
488            } catch (NameNotFoundException e) {
489                Log.e(TAG, "Could not create user package context");
490                return false;
491            }
492            Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
493            if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent)) {
494                if (DBG) Log.i(TAG, "matched AAR to application launch");
495                return true;
496            }
497            // Find the package in Market:
498            Intent marketIntent = getAppSearchIntent(firstPackage);
499            if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) {
500                if (DBG) Log.i(TAG, "matched AAR to market launch");
501                return true;
502            }
503        }
504
505        // regular launch
506        dispatch.intent.setPackage(null);
507        if (dispatch.tryStartActivity()) {
508            if (DBG) Log.i(TAG, "matched NDEF");
509            return true;
510        }
511
512        return false;
513    }
514
515    static List<String> extractAarPackages(NdefMessage message) {
516        List<String> aarPackages = new LinkedList<String>();
517        for (NdefRecord record : message.getRecords()) {
518            String pkg = checkForAar(record);
519            if (pkg != null) {
520                aarPackages.add(pkg);
521            }
522        }
523        return aarPackages;
524    }
525
526    boolean tryTech(DispatchInfo dispatch, Tag tag) {
527        dispatch.setTechIntent();
528
529        String[] tagTechs = tag.getTechList();
530        Arrays.sort(tagTechs);
531
532        // Standard tech dispatch path
533        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
534        List<ComponentInfo> registered = mTechListFilters.getComponents();
535
536        PackageManager pm;
537        try {
538            UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
539            pm = mContext.createPackageContextAsUser("android", 0,
540                    currentUser).getPackageManager();
541        } catch (NameNotFoundException e) {
542            Log.e(TAG, "Could not create user package context");
543            return false;
544        }
545        // Check each registered activity to see if it matches
546        for (ComponentInfo info : registered) {
547            // Don't allow wild card matching
548            if (filterMatch(tagTechs, info.techs) &&
549                    isComponentEnabled(pm, info.resolveInfo)) {
550                // Add the activity as a match if it's not already in the list
551                if (!matches.contains(info.resolveInfo)) {
552                    matches.add(info.resolveInfo);
553                }
554            }
555        }
556
557        if (matches.size() == 1) {
558            // Single match, launch directly
559            ResolveInfo info = matches.get(0);
560            dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
561            if (dispatch.tryStartActivity()) {
562                if (DBG) Log.i(TAG, "matched single TECH");
563                return true;
564            }
565            dispatch.intent.setComponent(null);
566        } else if (matches.size() > 1) {
567            // Multiple matches, show a custom activity chooser dialog
568            Intent intent = new Intent(mContext, TechListChooserActivity.class);
569            intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
570            intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
571                    matches);
572            if (dispatch.tryStartActivity(intent)) {
573                if (DBG) Log.i(TAG, "matched multiple TECH");
574                return true;
575            }
576        }
577        return false;
578    }
579
580    public boolean tryPeripheralHandover(NdefMessage m) {
581        if (m == null || !mDeviceSupportsBluetooth) return false;
582
583        if (DBG) Log.d(TAG, "tryHandover(): " + m.toString());
584
585        HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m);
586        if (handover == null || !handover.valid) return false;
587
588        Intent intent = new Intent(mContext, PeripheralHandoverService.class);
589        intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device);
590        intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name);
591        intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport);
592        mContext.startServiceAsUser(intent, UserHandle.CURRENT);
593
594        return true;
595    }
596
597
598    /**
599     * Tells the ActivityManager to resume allowing app switches.
600     *
601     * If the current app called stopAppSwitches() then our startActivity() can
602     * be delayed for several seconds. This happens with the default home
603     * screen.  As a system service we can override this behavior with
604     * resumeAppSwitches().
605    */
606    void resumeAppSwitches() {
607        try {
608            mIActivityManager.resumeAppSwitches();
609        } catch (RemoteException e) { }
610    }
611
612    /** Returns true if the tech list filter matches the techs on the tag */
613    boolean filterMatch(String[] tagTechs, String[] filterTechs) {
614        if (filterTechs == null || filterTechs.length == 0) return false;
615
616        for (String tech : filterTechs) {
617            if (Arrays.binarySearch(tagTechs, tech) < 0) {
618                return false;
619            }
620        }
621        return true;
622    }
623
624    static String checkForAar(NdefRecord record) {
625        if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
626                Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) {
627            return new String(record.getPayload(), StandardCharsets.US_ASCII);
628        }
629        return null;
630    }
631
632    /**
633     * Returns an intent that can be used to find an application not currently
634     * installed on the device.
635     */
636    static Intent getAppSearchIntent(String pkg) {
637        Intent market = new Intent(Intent.ACTION_VIEW);
638        market.setData(Uri.parse("market://details?id=" + pkg));
639        return market;
640    }
641
642    static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) {
643        boolean enabled = false;
644        ComponentName compname = new ComponentName(
645                info.activityInfo.packageName, info.activityInfo.name);
646        try {
647            // Note that getActivityInfo() will internally call
648            // isEnabledLP() to determine whether the component
649            // enabled. If it's not, null is returned.
650            if (pm.getActivityInfo(compname,0) != null) {
651                enabled = true;
652            }
653        } catch (PackageManager.NameNotFoundException e) {
654            enabled = false;
655        }
656        if (!enabled) {
657            Log.d(TAG, "Component not enabled: " + compname);
658        }
659        return enabled;
660    }
661
662    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
663        synchronized (this) {
664            pw.println("mOverrideIntent=" + mOverrideIntent);
665            pw.println("mOverrideFilters=" + mOverrideFilters);
666            pw.println("mOverrideTechLists=" + mOverrideTechLists);
667        }
668    }
669}
670