1 /*
2  * Copyright (C) 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 package com.android.server.credentials;
17 
18 import android.annotation.NonNull;
19 import android.app.PendingIntent;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.credentials.CredentialManager;
24 import android.credentials.CredentialProviderInfo;
25 import android.credentials.ui.DisabledProviderData;
26 import android.credentials.ui.IntentFactory;
27 import android.credentials.ui.ProviderData;
28 import android.credentials.ui.RequestInfo;
29 import android.credentials.ui.UserSelectionDialogResult;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.Looper;
34 import android.os.ResultReceiver;
35 import android.os.UserHandle;
36 import android.service.credentials.CredentialProviderInfoFactory;
37 import android.util.Slog;
38 
39 import java.util.ArrayList;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43 import java.util.UUID;
44 
45 /** Initiates the Credential Manager UI and receives results. */
46 public class CredentialManagerUi {
47     private static final String TAG = "CredentialManagerUi";
48     @NonNull
49     private final CredentialManagerUiCallback mCallbacks;
50     @NonNull
51     private final Context mContext;
52 
53     private final int mUserId;
54 
55     private UiStatus mStatus;
56 
57     private final Set<ComponentName> mEnabledProviders;
58 
59     enum UiStatus {
60         IN_PROGRESS,
61         USER_INTERACTION,
62         NOT_STARTED, TERMINATED
63     }
64 
65     @NonNull
66     private final ResultReceiver mResultReceiver = new ResultReceiver(
67             new Handler(Looper.getMainLooper())) {
68         @Override
69         protected void onReceiveResult(int resultCode, Bundle resultData) {
70             handleUiResult(resultCode, resultData);
71         }
72     };
73 
handleUiResult(int resultCode, Bundle resultData)74     private void handleUiResult(int resultCode, Bundle resultData) {
75 
76         switch (resultCode) {
77             case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION:
78                 mStatus = UiStatus.IN_PROGRESS;
79                 UserSelectionDialogResult selection = UserSelectionDialogResult
80                         .fromResultData(resultData);
81                 if (selection != null) {
82                     mCallbacks.onUiSelection(selection);
83                 } else {
84                     Slog.i(TAG, "No selection found in UI result");
85                 }
86                 break;
87             case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
88 
89                 mStatus = UiStatus.TERMINATED;
90                 mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
91                 break;
92             case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
93 
94                 mStatus = UiStatus.TERMINATED;
95                 mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
96                 break;
97             case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
98                 mStatus = UiStatus.TERMINATED;
99                 mCallbacks.onUiSelectorInvocationFailure();
100                 break;
101             default:
102                 mStatus = UiStatus.IN_PROGRESS;
103                 mCallbacks.onUiSelectorInvocationFailure();
104                 break;
105         }
106     }
107 
108     /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
createCancelIntent(IBinder requestId, String packageName)109     public Intent createCancelIntent(IBinder requestId, String packageName) {
110         return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
111                 packageName);
112     }
113 
114     /**
115      * Interface to be implemented by any class that wishes to get callbacks from the UI.
116      */
117     public interface CredentialManagerUiCallback {
118         /** Called when the user makes a selection. */
onUiSelection(UserSelectionDialogResult selection)119         void onUiSelection(UserSelectionDialogResult selection);
120 
121         /** Called when the UI is canceled without a successful provider result. */
onUiCancellation(boolean isUserCancellation)122         void onUiCancellation(boolean isUserCancellation);
123 
124         /** Called when the selector UI fails to come up (mostly due to parsing issue today). */
onUiSelectorInvocationFailure()125         void onUiSelectorInvocationFailure();
126     }
127 
CredentialManagerUi(Context context, int userId, CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders)128     public CredentialManagerUi(Context context, int userId,
129             CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) {
130         mContext = context;
131         mUserId = userId;
132         mCallbacks = callbacks;
133         mEnabledProviders = enabledProviders;
134         mStatus = UiStatus.IN_PROGRESS;
135     }
136 
137     /** Set status for credential manager UI */
setStatus(UiStatus status)138     public void setStatus(UiStatus status) {
139         mStatus = status;
140     }
141 
142     /** Returns status for credential manager UI */
getStatus()143     public UiStatus getStatus() {
144         return mStatus;
145     }
146 
147     /**
148      * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
149      * by the calling app process.
150      *
151      * @param requestInfo      the information about the request
152      * @param providerDataList the list of provider data from remote providers
153      */
createPendingIntent( RequestInfo requestInfo, ArrayList<ProviderData> providerDataList)154     public PendingIntent createPendingIntent(
155             RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
156         List<CredentialProviderInfo> allProviders =
157                 CredentialProviderInfoFactory.getCredentialProviderServices(
158                         mContext,
159                         mUserId,
160                         CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
161                         mEnabledProviders,
162                         // Don't need primary providers here.
163                         new HashSet<ComponentName>());
164 
165         List<DisabledProviderData> disabledProviderDataList = allProviders.stream()
166                 .filter(provider -> !provider.isEnabled())
167                 .map(disabledProvider -> new DisabledProviderData(
168                         disabledProvider.getComponentName().flattenToString())).toList();
169 
170         Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList,
171                         new ArrayList<>(disabledProviderDataList), mResultReceiver)
172                 .setAction(UUID.randomUUID().toString());
173         //TODO: Create unique pending intent using request code and cancel any pre-existing pending
174         // intents
175         return PendingIntent.getActivityAsUser(
176                 mContext, /*requestCode=*/0, intent,
177                 PendingIntent.FLAG_IMMUTABLE, /*options=*/null,
178                 UserHandle.of(mUserId));
179     }
180 }
181