Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 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.contacts.common.util;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.PackageManager;
     22 import android.content.pm.ResolveInfo;
     23 import android.net.Uri;
     24 import android.os.Build;
     25 import android.provider.ContactsContract.QuickContact;
     26 import android.text.TextUtils;
     27 
     28 import java.util.List;
     29 
     30 /**
     31  * Utility for forcing intents to be started inside the current app. This is useful for avoiding
     32  * senseless disambiguation dialogs. Ie, if a user clicks a contact inside Contacts we assume
     33  * they want to view the contact inside the Contacts app as opposed to a 3rd party contacts app.
     34  *
     35  * Methods are designed to replace the use of startActivity() for implicit intents. This class isn't
     36  * necessary for explicit intents. No attempt is made to replace startActivityForResult(), since
     37  * startActivityForResult() is always used with explicit intents in this project.
     38  *
     39  * Why not just always use explicit intents? The Contacts/Dialer app implements standard intent
     40  * actions used by others apps. We want to continue exercising these intent filters to make sure
     41  * they still work. Plus we sometimes don't know an explicit intent would work. See
     42  * {@link #startActivityInAppIfPossible}.
     43  *
     44  * Some ContactsCommon code that is only used by Dialer doesn't use ImplicitIntentsUtil.
     45  */
     46 public class ImplicitIntentsUtil {
     47 
     48     /**
     49      * Start an intent. If it is possible for this app to handle the intent, force this app's
     50      * activity to handle the intent. Sometimes it is impossible to know whether this app
     51      * can handle an intent while coding since the code is used inside both Dialer and Contacts.
     52      * This method is particularly useful in such circumstances.
     53      *
     54      * On a Nexus 5 with a small number of apps, this method consistently added 3-16ms of delay
     55      * in order to talk to the package manager.
     56      */
     57     public static void startActivityInAppIfPossible(Context context, Intent intent) {
     58         final Intent appIntent = getIntentInAppIfExists(context, intent);
     59         if (appIntent != null) {
     60             context.startActivity(appIntent);
     61         } else {
     62             context.startActivity(intent);
     63         }
     64     }
     65 
     66     /**
     67      * Start intent using an activity inside this app. This method is useful if you are certain
     68      * that the intent can be handled inside this app, and you care about shaving milliseconds.
     69      */
     70     public static void startActivityInApp(Context context, Intent intent) {
     71         String packageName = context.getPackageName();
     72         intent.setPackage(packageName);
     73         context.startActivity(intent);
     74     }
     75 
     76     /**
     77      * Start an intent normally. Assert that the intent can't be opened inside this app.
     78      */
     79     public static void startActivityOutsideApp(Context context, Intent intent) {
     80         final boolean isPlatformDebugBuild = Build.TYPE.equals("eng")
     81                 || Build.TYPE.equals("userdebug");
     82         if (isPlatformDebugBuild) {
     83             if (getIntentInAppIfExists(context, intent) != null) {
     84                 throw new AssertionError("startActivityOutsideApp() was called for an intent" +
     85                         " that can be handled inside the app");
     86             }
     87         }
     88         context.startActivity(intent);
     89     }
     90 
     91     /**
     92      * Returns an implicit intent for opening QuickContacts.
     93      */
     94     public static Intent composeQuickContactIntent(Uri contactLookupUri,
     95             int extraMode) {
     96         final Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
     97         intent.setData(contactLookupUri);
     98         intent.putExtra(QuickContact.EXTRA_MODE, extraMode);
     99         // Make sure not to show QuickContacts on top of another QuickContacts.
    100         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    101         return intent;
    102     }
    103 
    104     /**
    105      * Returns a copy of {@param intent} with a class name set, if a class inside this app
    106      * has a corresponding intent filter.
    107      */
    108     private static Intent getIntentInAppIfExists(Context context, Intent intent) {
    109         try {
    110             final Intent intentCopy = new Intent(intent);
    111             // Force this intentCopy to open inside the current app.
    112             intentCopy.setPackage(context.getPackageName());
    113             final List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(
    114                     intentCopy, PackageManager.MATCH_DEFAULT_ONLY);
    115             if (list != null && list.size() != 0) {
    116                 // Now that we know the intentCopy will work inside the current app, we
    117                 // can return this intent non-null.
    118                 if (list.get(0).activityInfo != null
    119                         && !TextUtils.isEmpty(list.get(0).activityInfo.name)) {
    120                     // Now that we know the class name, we may as well attach it to intentCopy
    121                     // to prevent the package manager from needing to find it again inside
    122                     // startActivity(). This is only needed for efficiency.
    123                     intentCopy.setClassName(context.getPackageName(),
    124                             list.get(0).activityInfo.name);
    125                 }
    126                 return intentCopy;
    127             }
    128             return null;
    129         } catch (Exception e) {
    130             // Don't let the package manager crash our app. If the package manager can't resolve the
    131             // intent here, then we can still call startActivity without calling setClass() first.
    132             return null;
    133         }
    134     }
    135 }
    136