1 /*
2  * Copyright (C) 2023 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.settings.biometrics.activeunlock;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ActivityInfo;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.ComponentInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ProviderInfo;
27 import android.provider.DeviceConfig;
28 import android.provider.Settings;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.StringRes;
34 
35 import com.android.settings.R;
36 import com.android.settings.Utils;
37 import com.android.settings.core.BasePreferenceController;
38 import com.android.settings.core.BasePreferenceController.AvailabilityStatus;
39 
40 /** Utilities for active unlock details shared between Security Settings and Safety Center. */
41 public class ActiveUnlockStatusUtils {
42 
43     /** The flag to determining whether active unlock in settings is enabled. */
44     public static final String CONFIG_FLAG_NAME = "active_unlock_in_settings";
45 
46     /** Flag value that represents the layout for unlock intent should be used. */
47     public static final String UNLOCK_INTENT_LAYOUT = "unlock_intent_layout";
48 
49     /** Flag value that represents the layout for biometric failure should be used. */
50     public static final String BIOMETRIC_FAILURE_LAYOUT = "biometric_failure_layout";
51 
52     private static final String ACTIVE_UNLOCK_PROVIDER = "active_unlock_provider";
53     private static final String ACTIVE_UNLOCK_TARGET = "active_unlock_target";
54 
55     private static final String TAG = "ActiveUnlockStatusUtils";
56 
57     private final Context mContext;
58     private final ContentResolver mContentResolver;
59 
ActiveUnlockStatusUtils(@onNull Context context)60     public ActiveUnlockStatusUtils(@NonNull Context context) {
61         mContext = context;
62         mContentResolver = mContext.getContentResolver();
63     }
64 
65     /** Returns whether the active unlock settings entity should be shown. */
isAvailable()66     public boolean isAvailable() {
67         return getAvailability() == BasePreferenceController.AVAILABLE;
68     }
69 
70     /**
71      * Returns whether the active unlock layout with the unlock on intent configuration should be
72      * used.
73      */
useUnlockIntentLayout()74     public boolean useUnlockIntentLayout() {
75         return isAvailable();
76     }
77 
78     /**
79      *
80      * Returns whether the active unlock layout with the unlock on biometric failure configuration
81      * should be used.
82      */
useBiometricFailureLayout()83     public boolean useBiometricFailureLayout() {
84         return false;
85     }
86 
87     /**
88      * Returns the authority used to fetch dynamic active unlock content.
89      */
90     @Nullable
getAuthority()91     public String getAuthority() {
92         final String authority = Settings.Secure.getString(
93                 mContext.getContentResolver(), ACTIVE_UNLOCK_PROVIDER);
94         if (authority == null) {
95             Log.i(TAG, "authority not set");
96             return null;
97         }
98         final ProviderInfo provider = mContext.getPackageManager().resolveContentProvider(
99                 authority, PackageManager.ComponentInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
100         if (provider == null) {
101             Log.i(TAG, "could not find provider");
102             return null;
103         }
104         if (authority.equals(provider.authority) && isSystemApp(provider)) {
105             return authority;
106         }
107         Log.e(TAG, "authority not valid");
108         return null;
109     }
110 
isSystemApp(ComponentInfo componentInfo)111     private static boolean isSystemApp(ComponentInfo componentInfo) {
112         final ApplicationInfo applicationInfo = componentInfo.applicationInfo;
113         if (applicationInfo == null) {
114             Log.e(TAG, "application info is null");
115             return false;
116         }
117         return applicationInfo.isSystemApp();
118     }
119 
120     /**
121      * Returns the intent used to launch the active unlock activity.
122      */
123     @Nullable
getIntent()124     public Intent getIntent() {
125         final String targetAction = Settings.Secure.getString(
126                 mContentResolver, ACTIVE_UNLOCK_TARGET);
127         if (targetAction == null) {
128             Log.i(TAG, "Target action not set");
129             return null;
130         }
131         final Intent intent = new Intent(targetAction);
132         final ActivityInfo activityInfo = intent.resolveActivityInfo(
133                 mContext.getPackageManager(), PackageManager.MATCH_ALL);
134         if (activityInfo == null) {
135             Log.e(TAG, "Target activity not found");
136             return null;
137         }
138         if (!isSystemApp(activityInfo)) {
139             Log.e(TAG, "Target application is not system");
140             return null;
141         }
142         Log.i(TAG, "Target application is valid");
143         return intent;
144     }
145 
146     /** Returns the availability status of the active unlock feature. */
147     @AvailabilityStatus
getAvailability()148     int getAvailability() {
149         if (!Utils.hasFingerprintHardware(mContext) && !Utils.hasFaceHardware(mContext)) {
150             return BasePreferenceController.UNSUPPORTED_ON_DEVICE;
151         }
152         if (getAuthority() != null && getIntent() != null) {
153             return BasePreferenceController.AVAILABLE;
154         }
155         return BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
156     }
157 
158     /**
159      * Returns the title of the combined biometric settings entity when active unlock is enabled.
160      */
getTitleForActiveUnlock()161     public String getTitleForActiveUnlock() {
162         final boolean faceAllowed = Utils.hasFaceHardware(mContext);
163         final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
164         return mContext.getString(getTitleRes(faceAllowed, fingerprintAllowed));
165     }
166 
167     @StringRes
getTitleRes(boolean isFaceAllowed, boolean isFingerprintAllowed)168     private static int getTitleRes(boolean isFaceAllowed, boolean isFingerprintAllowed) {
169         if (isFaceAllowed && isFingerprintAllowed) {
170             return R.string.security_settings_biometric_preference_title;
171         } else if (isFaceAllowed) {
172             return R.string.security_settings_face_preference_title;
173         } else if (isFingerprintAllowed) {
174             return R.string.security_settings_fingerprint_preference_title;
175         } else {
176             // Default to original summary, but this case should never happen.
177             return R.string.security_settings_biometric_preference_title;
178         }
179     }
180 
181     /**
182      * Returns the intro of the combined biometric settings entity when active unlock is enabled.
183      */
getIntroForActiveUnlock()184     public String getIntroForActiveUnlock() {
185         final boolean faceAllowed = Utils.hasFaceHardware(mContext);
186         final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
187         if (isAvailable()) {
188             int introRes = getIntroRes(faceAllowed, fingerprintAllowed);
189             return introRes == 0 ? "" : mContext.getString(introRes);
190         }
191         return mContext.getString(R.string.biometric_settings_intro);
192     }
193 
194     @StringRes
getIntroRes(boolean isFaceAllowed, boolean isFingerprintAllowed)195     private static int getIntroRes(boolean isFaceAllowed, boolean isFingerprintAllowed) {
196         if (isFaceAllowed && isFingerprintAllowed) {
197             return R.string.biometric_settings_intro_with_activeunlock;
198         } else if (isFaceAllowed) {
199             return R.string.biometric_settings_intro_with_face;
200         } else if (isFingerprintAllowed) {
201             return R.string.biometric_settings_intro_with_fingerprint;
202         } else {
203             return 0;
204         }
205     }
206 
207     /**
208      * Returns the summary of the unlock device entity when active unlock is enabled.
209      */
getUnlockDeviceSummaryForActiveUnlock()210     public String getUnlockDeviceSummaryForActiveUnlock() {
211         final boolean faceAllowed = Utils.hasFaceHardware(mContext);
212         final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
213 
214         return mContext.getString(getUnlockDeviceSummaryRes(faceAllowed, fingerprintAllowed));
215     }
216 
217     @StringRes
getUnlockDeviceSummaryRes( boolean isFaceAllowed, boolean isFingerprintAllowed)218     private static int getUnlockDeviceSummaryRes(
219             boolean isFaceAllowed, boolean isFingerprintAllowed) {
220         if (isFaceAllowed && isFingerprintAllowed) {
221             return R.string.biometric_settings_use_face_fingerprint_or_watch_preference_summary;
222         } else if (isFaceAllowed) {
223             return R.string.biometric_settings_use_face_or_watch_preference_summary;
224         } else if (isFingerprintAllowed) {
225             return R.string.biometric_settings_use_fingerprint_or_watch_preference_summary;
226         } else {
227             return R.string.biometric_settings_use_watch_preference_summary;
228         }
229     }
230 
231     /**
232      * Returns the summary of the active unlock preference when biometrics are needed to set up the
233      * feature.
234      */
235     @Nullable
getSummaryWhenBiometricSetupRequired()236     public String getSummaryWhenBiometricSetupRequired() {
237         final boolean faceAllowed = Utils.hasFaceHardware(mContext);
238         final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
239 
240         int summaryRes = getSetupBiometricRes(faceAllowed, fingerprintAllowed);
241         return summaryRes == 0 ? null : mContext.getString(summaryRes);
242     }
243 
244     @StringRes
getSetupBiometricRes(boolean faceAllowed, boolean fingerprintAllowed)245     private static int getSetupBiometricRes(boolean faceAllowed, boolean fingerprintAllowed) {
246         if (faceAllowed && fingerprintAllowed) {
247             return R.string.security_settings_activeunlock_require_face_fingerprint_setup_title;
248         } else if (faceAllowed) {
249             return R.string.security_settings_activeunlock_require_face_setup_title;
250         } else if (fingerprintAllowed) {
251             return R.string.security_settings_activeunlock_require_fingerprint_setup_title;
252         } else {
253             return 0;
254         }
255     }
256 
257     /**
258      * Returns the preference title of how to use biometrics when active unlock is enabled.
259      */
getUseBiometricTitleForActiveUnlock()260     public String getUseBiometricTitleForActiveUnlock() {
261         final boolean faceAllowed = Utils.hasFaceHardware(mContext);
262         final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
263 
264         return mContext.getString(getUseBiometricTitleRes(faceAllowed, fingerprintAllowed));
265     }
266 
267     @StringRes
getUseBiometricTitleRes( boolean isFaceAllowed, boolean isFingerprintAllowed)268     private static int getUseBiometricTitleRes(
269             boolean isFaceAllowed, boolean isFingerprintAllowed) {
270         if (isFaceAllowed && isFingerprintAllowed) {
271             return R.string.biometric_settings_use_face_fingerprint_or_watch_for;
272         } else if (isFaceAllowed) {
273             return R.string.biometric_settings_use_face_or_watch_for;
274         } else if (isFingerprintAllowed) {
275             return R.string.biometric_settings_use_fingerprint_or_watch_for;
276         } else {
277             return R.string.biometric_settings_use_watch_for;
278         }
279     }
280 
getFlagState()281     private static String getFlagState() {
282         return DeviceConfig.getProperty(DeviceConfig.NAMESPACE_REMOTE_AUTH, CONFIG_FLAG_NAME);
283     }
284 }
285