diff options
5 files changed, 125 insertions, 5 deletions
diff --git a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java index d3a7e6f6..831841cd 100644 --- a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java +++ b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java @@ -48,11 +48,11 @@ import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.AccountSelectionUtil; import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; +import com.android.contacts.common.util.ImplicitIntentsUtil; import com.android.contacts.common.vcard.ExportVCardActivity; import com.android.contacts.common.vcard.VCardCommonArguments; import com.android.contacts.commonbind.analytics.AnalyticsUtil; -import java.util.Collections; import java.util.List; /** @@ -220,7 +220,7 @@ public class ImportExportDialogFragment extends DialogFragment final Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(Contacts.CONTENT_VCARD_TYPE); intent.putExtra(Intent.EXTRA_STREAM, uri); - getActivity().startActivity(intent); + ImplicitIntentsUtil.startActivityOutsideApp(getActivity(), intent); } finally { cursor.close(); } diff --git a/src/com/android/contacts/common/list/ShortcutIntentBuilder.java b/src/com/android/contacts/common/list/ShortcutIntentBuilder.java index 6c97fd9f..2fcbfb9a 100644 --- a/src/com/android/contacts/common/list/ShortcutIntentBuilder.java +++ b/src/com/android/contacts/common/list/ShortcutIntentBuilder.java @@ -265,6 +265,9 @@ public class ShortcutIntentBuilder { String lookupKey, byte[] bitmapData) { Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey); + // Use an implicit intent without a package name set. It is reasonable for a disambiguation + // dialog to appear when opening QuickContacts from the launcher. Plus, this will be more + // resistant to future package name changes done to Contacts. Intent shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT); // When starting from the launcher, start in a new, cleared task. diff --git a/src/com/android/contacts/common/util/AccountSelectionUtil.java b/src/com/android/contacts/common/util/AccountSelectionUtil.java index cc3184d4..3e7b7d5a 100644 --- a/src/com/android/contacts/common/util/AccountSelectionUtil.java +++ b/src/com/android/contacts/common/util/AccountSelectionUtil.java @@ -22,7 +22,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; -import android.telephony.SubscriptionManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -185,7 +184,7 @@ public class AccountSelectionUtil { } importIntent.putExtra("subscription_id", (Integer) subscriptionId); importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts"); - context.startActivity(importIntent); + ImplicitIntentsUtil.startActivityOutsideApp(context, importIntent); } public static void doImportFromSdCard(Context context, AccountWithDataSet account) { diff --git a/src/com/android/contacts/common/util/ImplicitIntentsUtil.java b/src/com/android/contacts/common/util/ImplicitIntentsUtil.java new file mode 100644 index 00000000..d8c0d363 --- /dev/null +++ b/src/com/android/contacts/common/util/ImplicitIntentsUtil.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.contacts.common.util; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.provider.ContactsContract.QuickContact; + +import java.util.List; + +/** + * Utility for forcing intents to be started inside the current app. This is useful for avoiding + * senseless disambiguation dialogs. Ie, if a user clicks a contact inside Contacts we assume + * they want to view the contact inside the Contacts app as opposed to a 3rd party contacts app. + * + * Methods are designed to replace the use of startActivity() for implicit intents. This class isn't + * necessary for explicit intents. No attempt is made to replace startActivityForResult(), since + * startActivityForResult() is always used with explicit intents in this project. + * + * Why not just always use explicit intents? The Contacts/Dialer app implements standard intent + * actions used by others apps. We want to continue exercising these intent filters to make sure + * they still work. Plus we sometimes don't know an explicit intent would work. See + * {@link #startActivityInAppIfPossible}. + * + * Some ContactsCommon code that is only used by Dialer doesn't use ImplicitIntentsUtil. + */ +public class ImplicitIntentsUtil { + + /** + * Start an intent. If it is possible for this app to handle the intent, force this app's + * activity to handle the intent. Sometimes it is impossible to know whether this app + * can handle an intent while coding since the code is used inside both Dialer and Contacts. + * This method is particularly useful in such circumstances. + * + * On a Nexus 5 with a small number of apps, this method consistently added 3-16ms of delay + * in order to talk to the package manager. + */ + public static void startActivityInAppIfPossible(Context context, Intent intent) { + final Intent appIntent = getIntentInAppIfExists(context, intent); + if (appIntent != null) { + context.startActivity(appIntent); + } else { + context.startActivity(intent); + } + } + + /** + * Start intent using an activity inside this app. This method is useful if you are certain + * that the intent can be handled inside this app, and you care about shaving milliseconds. + */ + public static void startActivityInApp(Context context, Intent intent) { + String packageName = context.getPackageName(); + intent.setPackage(packageName); + context.startActivity(intent); + } + + /** + * Start an intent normally. Assert that the intent can't be opened inside this app. + */ + public static void startActivityOutsideApp(Context context, Intent intent) { + final boolean isPlatformDebugBuild = Build.TYPE.equals("eng") + || Build.TYPE.equals("userdebug"); + if (isPlatformDebugBuild) { + if (getIntentInAppIfExists(context, intent) != null) { + throw new AssertionError("startActivityOutsideApp() was called for an intent" + + " that can be handled inside the app"); + } + } + context.startActivity(intent); + } + + /** + * Returns an implicit intent for opening QuickContacts. + */ + public static Intent composeQuickContactIntent(Uri contactLookupUri, + int extraMode) { + final Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT); + intent.setData(contactLookupUri); + intent.putExtra(QuickContact.EXTRA_MODE, extraMode); + // Make sure not to show QuickContacts on top of another QuickContacts. + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return intent; + } + + /** + * Returns a copy of {@param intent} with a class name set, if a class inside this app + * has a corresponding intent filter. + */ + private static Intent getIntentInAppIfExists(Context context, Intent intent) { + final Intent intentCopy = new Intent(intent); + intentCopy.setPackage(context.getPackageName()); + final List<ResolveInfo> list = context.getPackageManager().queryIntentActivities( + intentCopy, 0); + if (list != null && list.size() != 0) { + intentCopy.setClass(context, list.get(0).getClass()); + return intentCopy; + } + return null; + } +} diff --git a/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java b/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java index 96f224a1..0837fbc4 100644 --- a/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java +++ b/src/com/android/contacts/common/vcard/NfcImportVCardActivity.java @@ -34,6 +34,7 @@ import android.util.Log; import com.android.contacts.common.R; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.util.ImplicitIntentsUtil; import com.android.vcard.VCardEntry; import com.android.vcard.VCardEntryCounter; import com.android.vcard.VCardParser; @@ -225,7 +226,7 @@ public class NfcImportVCardActivity extends Activity implements ServiceConnectio if (uri != null) { Uri contactUri = RawContacts.getContactLookupUri(getContentResolver(), uri); Intent intent = new Intent(Intent.ACTION_VIEW, contactUri); - startActivity(intent); + ImplicitIntentsUtil.startActivityInAppIfPossible(this, intent); finish(); } } |
