blob: 191c1854626e9419c12a8266477a5e6101c260ba [file] [log] [blame]
Brian Attwell03b64242015-02-19 21:33:55 -08001/*
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
Gary Mai69c182a2016-12-05 13:07:03 -080017package com.android.contacts.util;
Brian Attwell03b64242015-02-19 21:33:55 -080018
Gary Mai0a49afa2016-12-05 15:53:58 -080019import static com.android.contacts.list.ShortcutIntentBuilder.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION;
20
Walter Jang0396cf72016-09-21 13:23:29 -070021import android.app.Activity;
Brian Attwell03b64242015-02-19 21:33:55 -080022import android.content.Context;
23import android.content.Intent;
Brian Attwell35dab802015-03-04 11:03:41 -080024import android.content.pm.PackageManager;
Brian Attwell03b64242015-02-19 21:33:55 -080025import android.content.pm.ResolveInfo;
26import android.net.Uri;
27import android.os.Build;
guanxiongliu10be3bf2016-01-29 19:36:50 -080028import android.provider.ContactsContract;
Brian Attwell03b64242015-02-19 21:33:55 -080029import android.provider.ContactsContract.QuickContact;
guanxiongliu10be3bf2016-01-29 19:36:50 -080030import android.provider.Settings;
Brian Attwell91e64cd2015-03-04 11:49:32 -080031import android.text.TextUtils;
Brian Attwell03b64242015-02-19 21:33:55 -080032
Gary Mai0a49afa2016-12-05 15:53:58 -080033import com.android.contacts.Experiments;
Gary Mai69c182a2016-12-05 13:07:03 -080034import com.android.contacts.logging.ScreenEvent.ScreenType;
35import com.android.contacts.model.account.GoogleAccountType;
John Shao41c68862016-08-17 21:02:41 -070036import com.android.contacts.quickcontact.QuickContactActivity;
Walter Jang581585d2016-09-21 19:21:13 -070037import com.android.contactsbind.ObjectFactory;
38import com.android.contactsbind.experiments.Flags;
Walter Jangf13cb2f2016-07-14 12:41:52 -070039
Brian Attwell03b64242015-02-19 21:33:55 -080040import java.util.List;
41
42/**
43 * Utility for forcing intents to be started inside the current app. This is useful for avoiding
44 * senseless disambiguation dialogs. Ie, if a user clicks a contact inside Contacts we assume
45 * they want to view the contact inside the Contacts app as opposed to a 3rd party contacts app.
46 *
47 * Methods are designed to replace the use of startActivity() for implicit intents. This class isn't
48 * necessary for explicit intents. No attempt is made to replace startActivityForResult(), since
49 * startActivityForResult() is always used with explicit intents in this project.
50 *
51 * Why not just always use explicit intents? The Contacts/Dialer app implements standard intent
52 * actions used by others apps. We want to continue exercising these intent filters to make sure
53 * they still work. Plus we sometimes don't know an explicit intent would work. See
54 * {@link #startActivityInAppIfPossible}.
55 *
56 * Some ContactsCommon code that is only used by Dialer doesn't use ImplicitIntentsUtil.
57 */
58public class ImplicitIntentsUtil {
59
60 /**
61 * Start an intent. If it is possible for this app to handle the intent, force this app's
62 * activity to handle the intent. Sometimes it is impossible to know whether this app
63 * can handle an intent while coding since the code is used inside both Dialer and Contacts.
64 * This method is particularly useful in such circumstances.
65 *
66 * On a Nexus 5 with a small number of apps, this method consistently added 3-16ms of delay
67 * in order to talk to the package manager.
68 */
69 public static void startActivityInAppIfPossible(Context context, Intent intent) {
70 final Intent appIntent = getIntentInAppIfExists(context, intent);
71 if (appIntent != null) {
72 context.startActivity(appIntent);
73 } else {
74 context.startActivity(intent);
75 }
76 }
77
78 /**
79 * Start intent using an activity inside this app. This method is useful if you are certain
80 * that the intent can be handled inside this app, and you care about shaving milliseconds.
81 */
82 public static void startActivityInApp(Context context, Intent intent) {
83 String packageName = context.getPackageName();
84 intent.setPackage(packageName);
85 context.startActivity(intent);
86 }
87
88 /**
89 * Start an intent normally. Assert that the intent can't be opened inside this app.
90 */
91 public static void startActivityOutsideApp(Context context, Intent intent) {
92 final boolean isPlatformDebugBuild = Build.TYPE.equals("eng")
93 || Build.TYPE.equals("userdebug");
94 if (isPlatformDebugBuild) {
95 if (getIntentInAppIfExists(context, intent) != null) {
96 throw new AssertionError("startActivityOutsideApp() was called for an intent" +
97 " that can be handled inside the app");
98 }
99 }
100 context.startActivity(intent);
101 }
102
103 /**
Walter Jang0396cf72016-09-21 13:23:29 -0700104 * Starts QuickContact in app with the default mode and specified previous screen type.
105 */
106 public static void startQuickContact(Activity activity, Uri contactLookupUri,
107 int previousScreenType) {
108 startQuickContact(activity, contactLookupUri, previousScreenType, /* requestCode */ -1);
109 }
110
111 /**
112 * Starts QuickContact for result with the default mode and specified previous screen type.
113 */
114 public static void startQuickContactForResult(Activity activity, Uri contactLookupUri,
115 int previousScreenType, int requestCode) {
116 startQuickContact(activity, contactLookupUri, previousScreenType, requestCode);
117 }
118
119 private static void startQuickContact(Activity activity, Uri contactLookupUri,
120 int previousScreenType, int requestCode) {
Walter Jang581585d2016-09-21 19:21:13 -0700121
Walter Jangdf86ede2016-10-19 09:48:29 -0700122 if (Flags.getInstance().getBoolean(Experiments.CONTACT_SHEET)) {
Walter Jang581585d2016-09-21 19:21:13 -0700123 final Intent intent = ObjectFactory.getContactSheetIntent(activity, contactLookupUri);
124 if (intent != null) {
125 // We must start ContactSheet "for result" with a requestCode that is >= 0
126 // so that ContactSheet can validate that the caller is a 1P app.
127 activity.startActivityForResult(intent, requestCode >= 0 ? requestCode : 0);
128 return;
129 }
130 }
131
Walter Jang0396cf72016-09-21 13:23:29 -0700132 final Intent intent = ImplicitIntentsUtil.composeQuickContactIntent(
133 activity, contactLookupUri, previousScreenType);
134
Walter Jang581585d2016-09-21 19:21:13 -0700135 // For the non ContactSheet case we only start "for result" if specifically requested.
Walter Jang0396cf72016-09-21 13:23:29 -0700136 if (requestCode >= 0) {
Walter Jang581585d2016-09-21 19:21:13 -0700137 intent.setPackage(activity.getPackageName());
Walter Jang0396cf72016-09-21 13:23:29 -0700138 activity.startActivityForResult(intent, requestCode);
139 } else {
140 startActivityInApp(activity, intent);
141 }
142 }
143
144 /**
145 * Returns an implicit intent for opening QuickContacts with the default mode and specified
146 * previous screen type.
147 */
148 public static Intent composeQuickContactIntent(Context context, Uri contactLookupUri,
149 int previousScreenType) {
150 return composeQuickContactIntent(context, contactLookupUri,
151 QuickContactActivity.MODE_FULLY_EXPANDED, previousScreenType);
152 }
153
154 /**
Brian Attwell03b64242015-02-19 21:33:55 -0800155 * Returns an implicit intent for opening QuickContacts.
156 */
John Shao41c68862016-08-17 21:02:41 -0700157 public static Intent composeQuickContactIntent(Context context, Uri contactLookupUri,
Walter Jang0396cf72016-09-21 13:23:29 -0700158 int mode, int previousScreenType) {
John Shao41c68862016-08-17 21:02:41 -0700159 final Intent intent = new Intent(context, QuickContactActivity.class);
160 intent.setAction(QuickContact.ACTION_QUICK_CONTACT);
Brian Attwell03b64242015-02-19 21:33:55 -0800161 intent.setData(contactLookupUri);
Walter Jang0396cf72016-09-21 13:23:29 -0700162 intent.putExtra(QuickContact.EXTRA_MODE, mode);
Brian Attwell03b64242015-02-19 21:33:55 -0800163 // Make sure not to show QuickContacts on top of another QuickContacts.
164 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
Walter Jang0396cf72016-09-21 13:23:29 -0700165 intent.putExtra(QuickContactActivity.EXTRA_PREVIOUS_SCREEN_TYPE, previousScreenType);
Brian Attwell03b64242015-02-19 21:33:55 -0800166 return intent;
167 }
168
169 /**
Walter Jang8be77012016-07-07 21:05:34 -0700170 * Returns an Intent to open the Settings add account activity filtered to only
171 * display contact provider account types.
guanxiongliu10be3bf2016-01-29 19:36:50 -0800172 */
173 public static Intent getIntentForAddingAccount() {
Walter Jangf13cb2f2016-07-14 12:41:52 -0700174 final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
175 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
guanxiongliu10be3bf2016-01-29 19:36:50 -0800176 intent.putExtra(Settings.EXTRA_AUTHORITIES,
177 new String[]{ContactsContract.AUTHORITY});
178 return intent;
179 }
180
181 /**
Walter Jangf13cb2f2016-07-14 12:41:52 -0700182 * Returns an Intent to add a google account.
183 */
184 public static Intent getIntentForAddingGoogleAccount() {
185 final Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
186 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
187 intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES,
188 new String[]{GoogleAccountType.ACCOUNT_TYPE});
189 return intent;
190 }
191
Marcus Hagerottc5083f92016-09-14 08:34:29 -0700192 public static Intent getIntentForQuickContactLauncherShortcut(Context context, Uri contactUri) {
193 final Intent intent = composeQuickContactIntent(context, contactUri,
Walter Jang0396cf72016-09-21 13:23:29 -0700194 QuickContact.MODE_LARGE, ScreenType.UNKNOWN);
Marcus Hagerottc5083f92016-09-14 08:34:29 -0700195 intent.setPackage(context.getPackageName());
196
197 // When starting from the launcher, start in a new, cleared task.
198 // CLEAR_WHEN_TASK_RESET cannot reset the root of a task, so we
199 // clear the whole thing preemptively here since QuickContactActivity will
200 // finish itself when launching other detail activities. We need to use
201 // Intent.FLAG_ACTIVITY_NO_ANIMATION since not all versions of launcher will respect
202 // the INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION intent extra.
203 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
204 | Intent.FLAG_ACTIVITY_NO_ANIMATION);
205
206 // Tell the launcher to not do its animation, because we are doing our own
207 intent.putExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true);
208
209 intent.putExtra(QuickContact.EXTRA_EXCLUDE_MIMES, (String[])null);
210
211 return intent;
212 }
213
Walter Jangf13cb2f2016-07-14 12:41:52 -0700214 /**
Brian Attwell03b64242015-02-19 21:33:55 -0800215 * Returns a copy of {@param intent} with a class name set, if a class inside this app
216 * has a corresponding intent filter.
217 */
218 private static Intent getIntentInAppIfExists(Context context, Intent intent) {
Brian Attwell35dab802015-03-04 11:03:41 -0800219 try {
220 final Intent intentCopy = new Intent(intent);
Brian Attwell91e64cd2015-03-04 11:49:32 -0800221 // Force this intentCopy to open inside the current app.
Brian Attwell35dab802015-03-04 11:03:41 -0800222 intentCopy.setPackage(context.getPackageName());
223 final List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(
224 intentCopy, PackageManager.MATCH_DEFAULT_ONLY);
225 if (list != null && list.size() != 0) {
Brian Attwell91e64cd2015-03-04 11:49:32 -0800226 // Now that we know the intentCopy will work inside the current app, we
227 // can return this intent non-null.
228 if (list.get(0).activityInfo != null
229 && !TextUtils.isEmpty(list.get(0).activityInfo.name)) {
230 // Now that we know the class name, we may as well attach it to intentCopy
231 // to prevent the package manager from needing to find it again inside
232 // startActivity(). This is only needed for efficiency.
233 intentCopy.setClassName(context.getPackageName(),
234 list.get(0).activityInfo.name);
235 }
Brian Attwell35dab802015-03-04 11:03:41 -0800236 return intentCopy;
237 }
Brian Attwell91e64cd2015-03-04 11:49:32 -0800238 return null;
Brian Attwell35dab802015-03-04 11:03:41 -0800239 } catch (Exception e) {
240 // Don't let the package manager crash our app. If the package manager can't resolve the
241 // intent here, then we can still call startActivity without calling setClass() first.
242 return null;
Brian Attwell03b64242015-02-19 21:33:55 -0800243 }
Brian Attwell03b64242015-02-19 21:33:55 -0800244 }
245}