• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 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 android.credentials.selection;
18 
19 import static android.credentials.flags.Flags.FLAG_PROPAGATE_USER_CONTEXT_FOR_INTENT_CREATION;
20 import static android.credentials.flags.Flags.configurableSelectorUiEnabled;
21 
22 import android.annotation.FlaggedApi;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SuppressLint;
26 import android.annotation.TestApi;
27 import android.annotation.UserIdInt;
28 import android.app.AppGlobals;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.ActivityInfo;
33 import android.content.pm.PackageManager;
34 import android.content.res.Resources;
35 import android.os.IBinder;
36 import android.os.Parcel;
37 import android.os.RemoteException;
38 import android.os.ResultReceiver;
39 import android.text.TextUtils;
40 import android.util.Slog;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import java.util.ArrayList;
45 
46 /**
47  * Helpers for generating the intents and related extras parameters to launch the UI activities.
48  *
49  * @hide
50  */
51 @TestApi
52 @FlaggedApi(FLAG_PROPAGATE_USER_CONTEXT_FOR_INTENT_CREATION)
53 public class IntentFactory {
54 
55     /**
56      * Generate a new launch intent to the Credential Selector UI for auto-filling. This intent is
57      * invoked from the Autofill flow, when the user requests to bring up the 'All Options' page of
58      * the credential bottom-sheet. When the user clicks on the pinned entry, the intent will bring
59      * up the 'All Options' page of the bottom-sheet. The provider data list is processed by the
60      * credential autofill service for each autofill id and passed in as an auth extra.
61      *
62      * @hide
63      */
64     @NonNull
createCredentialSelectorIntentForAutofill( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver, @UserIdInt int userId)65     public static IntentCreationResult createCredentialSelectorIntentForAutofill(
66             @NonNull Context context,
67             @NonNull RequestInfo requestInfo,
68             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
69             @NonNull
70             ArrayList<DisabledProviderData> disabledProviderDataList,
71             @NonNull ResultReceiver resultReceiver,
72             @UserIdInt int userId) {
73         return createCredentialSelectorIntentInternal(context, requestInfo,
74                 disabledProviderDataList, resultReceiver, userId);
75     }
76 
77     /**
78      * Generate a new launch intent to the Credential Selector UI.
79      *
80      * @param context                  the CredentialManager system service (only expected caller)
81      *                                 context that may be used to query existence of the key UI
82      *                                 application
83      * @param disabledProviderDataList the list of disabled provider data that when non-empty the
84      *                                 UI should accordingly generate an entry suggesting the user
85      *                                 to navigate to settings and enable them
86      * @param enabledProviderDataList  the list of enabled provider that contain options for this
87      *                                 request; the UI should render each option to the user for
88      *                                 selection
89      * @param requestInfo              the display information about the given app request
90      * @param resultReceiver           used by the UI to send the UI selection result back
91      * @hide
92      */
93     @NonNull
createCredentialSelectorIntentForCredMan( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<ProviderData> enabledProviderDataList, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver, @UserIdInt int userId)94     public static IntentCreationResult createCredentialSelectorIntentForCredMan(
95             @NonNull Context context,
96             @NonNull RequestInfo requestInfo,
97             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
98             @NonNull
99             ArrayList<ProviderData> enabledProviderDataList,
100             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
101             @NonNull
102             ArrayList<DisabledProviderData> disabledProviderDataList,
103             @NonNull ResultReceiver resultReceiver,
104             @UserIdInt int userId) {
105         IntentCreationResult result = createCredentialSelectorIntentInternal(context, requestInfo,
106                 disabledProviderDataList, resultReceiver, userId);
107         result.getIntent().putParcelableArrayListExtra(
108                 ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
109         return result;
110     }
111 
112     /**
113      * Generate a new launch intent to the Credential Selector UI.
114      *
115      * @param context                  the CredentialManager system service (only expected caller)
116      *                                 context that may be used to query existence of the key UI
117      *                                 application
118      * @param disabledProviderDataList the list of disabled provider data that when non-empty the
119      *                                 UI should accordingly generate an entry suggesting the user
120      *                                 to navigate to settings and enable them
121      * @param enabledProviderDataList  the list of enabled provider that contain options for this
122      *                                 request; the UI should render each option to the user for
123      *                                 selection
124      * @param requestInfo              the display information about the given app request
125      * @param resultReceiver           used by the UI to send the UI selection result back
126      */
127     @VisibleForTesting
128     @NonNull
createCredentialSelectorIntent( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<ProviderData> enabledProviderDataList, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver, @UserIdInt int userId)129     public static Intent createCredentialSelectorIntent(
130             @NonNull Context context,
131             @NonNull RequestInfo requestInfo,
132             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
133             @NonNull
134             ArrayList<ProviderData> enabledProviderDataList,
135             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
136             @NonNull
137             ArrayList<DisabledProviderData> disabledProviderDataList,
138             @NonNull ResultReceiver resultReceiver, @UserIdInt int userId) {
139         return createCredentialSelectorIntentForCredMan(context, requestInfo,
140             enabledProviderDataList, disabledProviderDataList, resultReceiver,
141             userId).getIntent();
142     }
143 
144     /**
145      * Creates an Intent that cancels any UI matching the given request token id.
146      */
147     @VisibleForTesting
148     @NonNull
createCancelUiIntent(@onNull Context context, @NonNull IBinder requestToken, boolean shouldShowCancellationUi, @NonNull String appPackageName, @UserIdInt int userId)149     public static Intent createCancelUiIntent(@NonNull Context context,
150             @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
151             @NonNull String appPackageName, @UserIdInt int userId) {
152         Intent intent = new Intent();
153         IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
154         setCredentialSelectorUiComponentName(context, intent, intentResultBuilder, userId);
155         intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
156                 new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
157                         appPackageName));
158         return intent;
159     }
160 
161     /**
162      * Generate a new launch intent to the Credential Selector UI.
163      */
164     @NonNull
createCredentialSelectorIntentInternal( @onNull Context context, @NonNull RequestInfo requestInfo, @SuppressLint("ConcreteCollection") @NonNull ArrayList<DisabledProviderData> disabledProviderDataList, @NonNull ResultReceiver resultReceiver, @UserIdInt int userId)165     private static IntentCreationResult createCredentialSelectorIntentInternal(
166             @NonNull Context context,
167             @NonNull RequestInfo requestInfo,
168             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
169             @NonNull
170             ArrayList<DisabledProviderData> disabledProviderDataList,
171             @NonNull ResultReceiver resultReceiver, @UserIdInt int userId) {
172         Intent intent = new Intent();
173         IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
174         setCredentialSelectorUiComponentName(context, intent, intentResultBuilder, userId);
175         intent.putParcelableArrayListExtra(
176                 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
177         intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
178         intent.putExtra(
179                 Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver));
180         return intentResultBuilder.build();
181     }
182 
setCredentialSelectorUiComponentName(@onNull Context context, @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder, @UserIdInt int userId)183     private static void setCredentialSelectorUiComponentName(@NonNull Context context,
184             @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder,
185             @UserIdInt int userId) {
186         if (configurableSelectorUiEnabled()) {
187             ComponentName componentName = getOemOverrideComponentName(context,
188                     intentResultBuilder, userId);
189 
190             ComponentName fallbackUiComponentName = null;
191             try {
192                 fallbackUiComponentName = ComponentName.unflattenFromString(
193                         Resources.getSystem().getString(
194                                 com.android.internal.R.string
195                                         .config_fallbackCredentialManagerDialogComponent));
196                 intentResultBuilder.setFallbackUiPackageName(
197                         fallbackUiComponentName.getPackageName());
198             } catch (Exception e) {
199                 Slog.w(TAG, "Fallback CredMan IU not found: " + e);
200             }
201 
202             if (componentName == null) {
203                 componentName = fallbackUiComponentName;
204             }
205 
206             intent.setComponent(componentName);
207         } else {
208             ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
209                     .getString(com.android.internal.R.string
210                             .config_fallbackCredentialManagerDialogComponent));
211             intent.setComponent(componentName);
212         }
213     }
214 
215     /**
216      * Returns null if there is not an enabled and valid oem override component. It means the
217      * default platform UI component name should be used instead.
218      */
219     @Nullable
getOemOverrideComponentName(@onNull Context context, @NonNull IntentCreationResult.Builder intentResultBuilder, @UserIdInt int userId)220     private static ComponentName getOemOverrideComponentName(@NonNull Context context,
221             @NonNull IntentCreationResult.Builder intentResultBuilder, @UserIdInt int userId) {
222         ComponentName result = null;
223         String oemComponentString =
224                 Resources.getSystem()
225                         .getString(
226                                 com.android.internal.R.string
227                                         .config_oemCredentialManagerDialogComponent);
228         if (!TextUtils.isEmpty(oemComponentString)) {
229             ComponentName oemComponentName = null;
230             try {
231                 oemComponentName = ComponentName.unflattenFromString(
232                         oemComponentString);
233             } catch (Exception e) {
234                 Slog.i(TAG, "Failed to parse OEM component name " + oemComponentString + ": " + e);
235             }
236             if (oemComponentName != null) {
237                 try {
238                     intentResultBuilder.setOemUiPackageName(oemComponentName.getPackageName());
239                     ActivityInfo info;
240                     if (android.credentials.flags.Flags.propagateUserContextForIntentCreation()) {
241                       info = context.getPackageManager().getActivityInfo(oemComponentName,
242                           PackageManager.ComponentInfoFlags.of(
243                               PackageManager.MATCH_SYSTEM_ONLY));
244                     } else {
245                       info = AppGlobals.getPackageManager().getActivityInfo(
246                           oemComponentName, 0, userId);
247                     }
248                     boolean oemComponentEnabled = false;
249                     if (info != null) {
250                       oemComponentEnabled = info.enabled;
251                       int runtimeComponentEnabledState = context.getPackageManager()
252                           .getComponentEnabledSetting(oemComponentName);
253                       if (runtimeComponentEnabledState == PackageManager
254                           .COMPONENT_ENABLED_STATE_ENABLED) {
255                         oemComponentEnabled = true;
256                       } else if (runtimeComponentEnabledState == PackageManager
257                           .COMPONENT_ENABLED_STATE_DISABLED) {
258                         oemComponentEnabled = false;
259                       }
260                       if (oemComponentEnabled && info.exported) {
261                         intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
262                             .OemUiUsageStatus.SUCCESS);
263                         Slog.i(TAG,
264                             "Found enabled oem CredMan UI component."
265                                 + oemComponentString);
266                         result = oemComponentName;
267                       } else {
268                           intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
269                                   .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED);
270                           Slog.i(TAG,
271                                   "Found enabled oem CredMan UI component but it was not "
272                                       + "enabled.");
273                       }
274                     }
275                 } catch (RemoteException | PackageManager.NameNotFoundException e) {
276                     intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
277                             .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
278                     Slog.i(TAG, "Unable to find oem CredMan UI component: "
279                             + oemComponentString + ".");
280                 }
281             } else {
282                 intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
283                         .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
284                 Slog.i(TAG, "Invalid OEM ComponentName format.");
285             }
286         } else {
287             intentResultBuilder.setOemUiUsageStatus(
288                     IntentCreationResult.OemUiUsageStatus.OEM_UI_CONFIG_NOT_SPECIFIED);
289             Slog.i(TAG, "Invalid empty OEM component name.");
290         }
291         return result;
292     }
293 
294     /**
295      * Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
296      * android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
297      */
toIpcFriendlyResultReceiver( T resultReceiver)298     private static <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
299             T resultReceiver) {
300         final Parcel parcel = Parcel.obtain();
301         resultReceiver.writeToParcel(parcel, 0);
302         parcel.setDataPosition(0);
303 
304         final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
305         parcel.recycle();
306 
307         return ipcFriendly;
308     }
309 
IntentFactory()310     private IntentFactory() {
311     }
312 
313     private static final String TAG = "CredManIntentHelper";
314 }
315