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 */ startActivityInAppIfPossible(Context context, Intent intent)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 */ startActivityInApp(Context context, Intent intent)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 */ startActivityOutsideApp(Context context, Intent intent)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 */ composeQuickContactIntent(Uri contactLookupUri, int extraMode)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 */ getIntentInAppIfExists(Context context, Intent intent)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