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