1 /*
2  * Copyright (C) 2016 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.tv.settings.system;
18 
19 import android.accounts.AccountManager;
20 import android.annotation.SuppressLint;
21 import android.app.Fragment;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.pm.UserInfo;
29 import android.graphics.Bitmap;
30 import android.graphics.Canvas;
31 import android.graphics.drawable.Drawable;
32 import android.os.AsyncTask;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.ServiceManager;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.provider.Settings;
39 import android.text.TextUtils;
40 import android.util.Log;
41 
42 import androidx.annotation.DrawableRes;
43 import androidx.annotation.IntDef;
44 import androidx.annotation.Keep;
45 import androidx.leanback.preference.LeanbackSettingsFragment;
46 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
47 import androidx.preference.Preference;
48 import androidx.preference.PreferenceGroup;
49 import androidx.preference.TwoStatePreference;
50 
51 import com.android.internal.logging.nano.MetricsProto;
52 import com.android.internal.widget.ILockSettings;
53 import com.android.internal.widget.LockPatternUtils;
54 import com.android.tv.settings.R;
55 import com.android.tv.settings.SettingsPreferenceFragment;
56 import com.android.tv.settings.dialog.PinDialogFragment;
57 import com.android.tv.settings.users.AppRestrictionsFragment;
58 import com.android.tv.settings.users.RestrictedProfileModel;
59 import com.android.tv.settings.users.RestrictedProfilePinDialogFragment;
60 import com.android.tv.settings.users.UserSwitchListenerService;
61 
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.util.List;
65 
66 /**
67  * The security settings screen in Tv settings.
68  */
69 @Keep
70 public class SecurityFragment extends SettingsPreferenceFragment
71         implements RestrictedProfilePinDialogFragment.Callback {
72 
73     private static final String TAG = "SecurityFragment";
74 
75     private static final String KEY_UNKNOWN_SOURCES = "unknown_sources";
76     private static final String KEY_VERIFY_APPS = "verify_apps";
77     private static final String KEY_RESTRICTED_PROFILE_GROUP = "restricted_profile_group";
78     private static final String KEY_RESTRICTED_PROFILE_ENTER = "restricted_profile_enter";
79     private static final String KEY_RESTRICTED_PROFILE_EXIT = "restricted_profile_exit";
80     private static final String KEY_RESTRICTED_PROFILE_APPS = "restricted_profile_apps";
81     private static final String KEY_RESTRICTED_PROFILE_PIN = "restricted_profile_pin";
82     private static final String KEY_RESTRICTED_PROFILE_CREATE = "restricted_profile_create";
83     private static final String KEY_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete";
84 
85     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
86 
87     private static final String ACTION_RESTRICTED_PROFILE_CREATED =
88             "SecurityFragment.RESTRICTED_PROFILE_CREATED";
89     private static final String EXTRA_RESTRICTED_PROFILE_INFO =
90             "SecurityFragment.RESTRICTED_PROFILE_INFO";
91     private static final String SAVESTATE_CREATING_RESTRICTED_PROFILE =
92             "SecurityFragment.CREATING_RESTRICTED_PROFILE";
93 
94     @Retention(RetentionPolicy.SOURCE)
95     @IntDef({PIN_MODE_CHOOSE_LOCKSCREEN,
96             PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT,
97             PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD,
98             PIN_MODE_RESTRICTED_PROFILE_DELETE})
99     private @interface PinMode {}
100     private static final int PIN_MODE_CHOOSE_LOCKSCREEN = 1;
101     private static final int PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT = 2;
102     private static final int PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD = 3;
103     private static final int PIN_MODE_RESTRICTED_PROFILE_DELETE = 4;
104 
105     private Preference mUnknownSourcesPref;
106     private TwoStatePreference mVerifyAppsPref;
107     private PreferenceGroup mRestrictedProfileGroup;
108     private Preference mRestrictedProfileEnterPref;
109     private Preference mRestrictedProfileExitPref;
110     private Preference mRestrictedProfileAppsPref;
111     private Preference mRestrictedProfilePinPref;
112     private Preference mRestrictedProfileCreatePref;
113     private Preference mRestrictedProfileDeletePref;
114 
115     private ILockSettings mLockSettingsService;
116     private RestrictedProfileModel mRestrictedProfile;
117 
118     private boolean mCreatingRestrictedProfile;
119     @SuppressLint("StaticFieldLeak")
120     private static CreateRestrictedProfileTask sCreateRestrictedProfileTask;
121     private final BroadcastReceiver mRestrictedProfileReceiver = new BroadcastReceiver() {
122         @Override
123         public void onReceive(Context context, Intent intent) {
124             UserInfo result = intent.getParcelableExtra(EXTRA_RESTRICTED_PROFILE_INFO);
125             if (isResumed()) {
126                 onRestrictedUserCreated(result);
127             }
128         }
129     };
130 
131     private final Handler mHandler = new Handler();
132 
newInstance()133     public static SecurityFragment newInstance() {
134         return new SecurityFragment();
135     }
136 
137     @Override
onCreate(Bundle savedInstanceState)138     public void onCreate(Bundle savedInstanceState) {
139         mRestrictedProfile = new RestrictedProfileModel(getContext());
140 
141         super.onCreate(savedInstanceState);
142         mCreatingRestrictedProfile = savedInstanceState != null
143                 && savedInstanceState.getBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE);
144     }
145 
146     @Override
onResume()147     public void onResume() {
148         super.onResume();
149         refresh();
150         LocalBroadcastManager.getInstance(getActivity())
151                 .registerReceiver(mRestrictedProfileReceiver,
152                         new IntentFilter(ACTION_RESTRICTED_PROFILE_CREATED));
153         if (mCreatingRestrictedProfile) {
154             UserInfo userInfo = mRestrictedProfile.getUser();
155             if (userInfo != null) {
156                 onRestrictedUserCreated(userInfo);
157             }
158         }
159     }
160 
161     @Override
onPause()162     public void onPause() {
163         super.onPause();
164         LocalBroadcastManager.getInstance(getActivity())
165                 .unregisterReceiver(mRestrictedProfileReceiver);
166     }
167 
168     @Override
onSaveInstanceState(Bundle outState)169     public void onSaveInstanceState(Bundle outState) {
170         super.onSaveInstanceState(outState);
171         outState.putBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE, mCreatingRestrictedProfile);
172     }
173 
174     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)175     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
176         setPreferencesFromResource(R.xml.security, null);
177 
178         mUnknownSourcesPref = findPreference(KEY_UNKNOWN_SOURCES);
179         mVerifyAppsPref = (TwoStatePreference) findPreference(KEY_VERIFY_APPS);
180         mRestrictedProfileGroup = (PreferenceGroup) findPreference(KEY_RESTRICTED_PROFILE_GROUP);
181         mRestrictedProfileEnterPref = findPreference(KEY_RESTRICTED_PROFILE_ENTER);
182         mRestrictedProfileExitPref = findPreference(KEY_RESTRICTED_PROFILE_EXIT);
183         mRestrictedProfileAppsPref = findPreference(KEY_RESTRICTED_PROFILE_APPS);
184         mRestrictedProfilePinPref = findPreference(KEY_RESTRICTED_PROFILE_PIN);
185         mRestrictedProfileCreatePref = findPreference(KEY_RESTRICTED_PROFILE_CREATE);
186         mRestrictedProfileDeletePref = findPreference(KEY_RESTRICTED_PROFILE_DELETE);
187     }
188 
refresh()189     private void refresh() {
190         if (mRestrictedProfile.isCurrentUser()) {
191             // We are in restricted profile
192             mUnknownSourcesPref.setVisible(false);
193             mVerifyAppsPref.setVisible(false);
194 
195             mRestrictedProfileGroup.setVisible(true);
196             mRestrictedProfileEnterPref.setVisible(false);
197             mRestrictedProfileExitPref.setVisible(true);
198             mRestrictedProfileAppsPref.setVisible(false);
199             mRestrictedProfilePinPref.setVisible(false);
200             mRestrictedProfileCreatePref.setVisible(false);
201             mRestrictedProfileDeletePref.setVisible(false);
202         } else if (mRestrictedProfile.getUser() != null) {
203             // Not in restricted profile, but it exists
204             mUnknownSourcesPref.setVisible(true);
205             mVerifyAppsPref.setVisible(shouldShowVerifierSetting());
206 
207             mRestrictedProfileGroup.setVisible(true);
208             mRestrictedProfileEnterPref.setVisible(true);
209             mRestrictedProfileExitPref.setVisible(false);
210             mRestrictedProfileAppsPref.setVisible(true);
211             mRestrictedProfilePinPref.setVisible(true);
212             mRestrictedProfileCreatePref.setVisible(false);
213             mRestrictedProfileDeletePref.setVisible(true);
214 
215             AppRestrictionsFragment.prepareArgs(mRestrictedProfileAppsPref.getExtras(),
216                     mRestrictedProfile.getUser().id, false);
217         } else if (UserManager.supportsMultipleUsers()) {
218             // Not in restricted profile, and it doesn't exist
219             mUnknownSourcesPref.setVisible(true);
220             mVerifyAppsPref.setVisible(shouldShowVerifierSetting());
221 
222             mRestrictedProfileGroup.setVisible(true);
223             mRestrictedProfileEnterPref.setVisible(false);
224             mRestrictedProfileExitPref.setVisible(false);
225             mRestrictedProfileAppsPref.setVisible(false);
226             mRestrictedProfilePinPref.setVisible(false);
227             mRestrictedProfileCreatePref.setVisible(true);
228             mRestrictedProfileDeletePref.setVisible(false);
229         } else {
230             // Not in restricted profile, and can't create one either
231             mUnknownSourcesPref.setVisible(true);
232             mVerifyAppsPref.setVisible(shouldShowVerifierSetting());
233 
234             mRestrictedProfileGroup.setVisible(false);
235             mRestrictedProfileEnterPref.setVisible(false);
236             mRestrictedProfileExitPref.setVisible(false);
237             mRestrictedProfileAppsPref.setVisible(false);
238             mRestrictedProfilePinPref.setVisible(false);
239             mRestrictedProfileCreatePref.setVisible(false);
240             mRestrictedProfileDeletePref.setVisible(false);
241         }
242 
243         mRestrictedProfileCreatePref.setEnabled(sCreateRestrictedProfileTask == null);
244 
245         mUnknownSourcesPref.setEnabled(!isUnknownSourcesBlocked());
246         mVerifyAppsPref.setChecked(isVerifyAppsEnabled());
247         mVerifyAppsPref.setEnabled(isVerifierInstalled());
248     }
249 
250     @Override
onPreferenceTreeClick(Preference preference)251     public boolean onPreferenceTreeClick(Preference preference) {
252         final String key = preference.getKey();
253         if (TextUtils.isEmpty(key)) {
254             return super.onPreferenceTreeClick(preference);
255         }
256         switch (key) {
257             case KEY_VERIFY_APPS:
258                 setVerifyAppsEnabled(mVerifyAppsPref.isChecked());
259                 return true;
260             case KEY_RESTRICTED_PROFILE_ENTER:
261                 if (mRestrictedProfile.enterUser()) {
262                     getActivity().finish();
263                 }
264                 return true;
265             case KEY_RESTRICTED_PROFILE_EXIT:
266                 if (hasLockscreenSecurity()) {
267                     launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT);
268                 } else {
269                     pinFragmentDone(PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT, /* success= */ true);
270                 }
271                 return true;
272             case KEY_RESTRICTED_PROFILE_PIN:
273                 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD);
274                 return true;
275             case KEY_RESTRICTED_PROFILE_CREATE:
276                 if (hasLockscreenSecurity()) {
277                     addRestrictedUser();
278                 } else {
279                     launchPinDialog(PIN_MODE_CHOOSE_LOCKSCREEN);
280                 }
281                 return true;
282             case KEY_RESTRICTED_PROFILE_DELETE:
283                 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_DELETE);
284                 return true;
285         }
286         return super.onPreferenceTreeClick(preference);
287     }
288 
isUnknownSourcesBlocked()289     private boolean isUnknownSourcesBlocked() {
290         final UserManager um = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
291         return um.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
292     }
293 
isVerifyAppsEnabled()294     private boolean isVerifyAppsEnabled() {
295         return Settings.Global.getInt(getContext().getContentResolver(),
296                 Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) > 0 && isVerifierInstalled();
297     }
298 
setVerifyAppsEnabled(boolean enable)299     private void setVerifyAppsEnabled(boolean enable) {
300         Settings.Global.putInt(getContext().getContentResolver(),
301                 Settings.Global.PACKAGE_VERIFIER_ENABLE, enable ? 1 : 0);
302     }
303 
isVerifierInstalled()304     private boolean isVerifierInstalled() {
305         final PackageManager pm = getContext().getPackageManager();
306         final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
307         verification.setType(PACKAGE_MIME_TYPE);
308         verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
309         final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(verification, 0);
310         return receivers.size() > 0;
311     }
312 
shouldShowVerifierSetting()313     private boolean shouldShowVerifierSetting() {
314         return Settings.Global.getInt(getContext().getContentResolver(),
315                 Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE, 1) > 0;
316     }
317 
launchPinDialog(@inMode int pinMode)318     private void launchPinDialog(@PinMode int pinMode) {
319         @PinDialogFragment.PinDialogType
320         int pinDialogMode;
321 
322         switch (pinMode) {
323             case PIN_MODE_CHOOSE_LOCKSCREEN:
324                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN;
325                 break;
326             case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT:
327                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN;
328                 break;
329             case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD:
330                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN;
331                 break;
332             case PIN_MODE_RESTRICTED_PROFILE_DELETE:
333                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_DELETE_PIN;
334                 break;
335             default:
336                 throw new IllegalArgumentException("Unknown pin mode: " + pinMode);
337         }
338 
339         RestrictedProfilePinDialogFragment restrictedProfilePinDialogFragment =
340                 RestrictedProfilePinDialogFragment.newInstance(pinDialogMode);
341         restrictedProfilePinDialogFragment.setTargetFragment(this, pinMode);
342         restrictedProfilePinDialogFragment.show(getFragmentManager(),
343                 PinDialogFragment.DIALOG_TAG);
344     }
345 
346     @Override
saveLockPassword(String pin, String originalPin, int quality)347     public void saveLockPassword(String pin, String originalPin, int quality) {
348         new LockPatternUtils(getActivity())
349                 .saveLockPassword(pin, originalPin, quality, getContext().getUserId());
350     }
351 
352     @Override
clearLockPassword(String oldPin)353     public void clearLockPassword(String oldPin) {
354         byte[] oldPinBytes = oldPin != null ? oldPin.getBytes() : null;
355         new LockPatternUtils(getActivity()).clearLock(oldPinBytes, getContext().getUserId());
356     }
357 
358     @Override
checkPassword(String password)359     public boolean checkPassword(String password) {
360         return mRestrictedProfile.checkPassword(password);
361     }
362 
363     @Override
hasLockscreenSecurity()364     public boolean hasLockscreenSecurity() {
365         return mRestrictedProfile.hasLockscreenSecurity();
366     }
367 
getLockSettings()368     private ILockSettings getLockSettings() {
369         if (mLockSettingsService == null) {
370             mLockSettingsService = ILockSettings.Stub.asInterface(
371                     ServiceManager.getService("lock_settings"));
372         }
373         return mLockSettingsService;
374     }
375 
376     @Override
pinFragmentDone(int requestCode, boolean success)377     public void pinFragmentDone(int requestCode, boolean success) {
378         switch (requestCode) {
379             case PIN_MODE_CHOOSE_LOCKSCREEN:
380                 if (success) {
381                     addRestrictedUser();
382                 }
383                 break;
384             case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT:
385                 if (success) {
386                     mRestrictedProfile.exitUser();
387                     getActivity().finish();
388                 }
389                 break;
390             case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD:
391                 // do nothing
392                 break;
393             case PIN_MODE_RESTRICTED_PROFILE_DELETE:
394                 if (success) {
395                     mHandler.post(() -> {
396                         mRestrictedProfile.removeUser();
397                         UserSwitchListenerService.updateLaunchPoint(getActivity(), false);
398                         refresh();
399                     });
400 
401                 }
402                 break;
403         }
404     }
405 
addRestrictedUser()406     private void addRestrictedUser() {
407         if (sCreateRestrictedProfileTask == null) {
408             sCreateRestrictedProfileTask = new CreateRestrictedProfileTask(getContext());
409             sCreateRestrictedProfileTask.execute();
410             mCreatingRestrictedProfile = true;
411         }
412         refresh();
413     }
414 
415     /**
416       * Called by other Fragments to decide whether to show or hide profile-related views.
417       */
isRestrictedProfileInEffect(Context context)418     public static boolean isRestrictedProfileInEffect(Context context) {
419         return new RestrictedProfileModel(context).isCurrentUser();
420     }
421 
onRestrictedUserCreated(UserInfo result)422     private void onRestrictedUserCreated(UserInfo result) {
423         int userId = result.id;
424         if (result.isRestricted()
425                 && result.restrictedProfileParentId == UserHandle.myUserId()) {
426             final AppRestrictionsFragment restrictionsFragment =
427                     AppRestrictionsFragment.newInstance(userId, true);
428             final Fragment settingsFragment = getCallbackFragment();
429             if (settingsFragment instanceof LeanbackSettingsFragment) {
430                 ((LeanbackSettingsFragment) settingsFragment)
431                         .startPreferenceFragment(restrictionsFragment);
432             } else {
433                 throw new IllegalStateException("Didn't find fragment of expected type: "
434                         + settingsFragment);
435             }
436         }
437         mCreatingRestrictedProfile = false;
438         refresh();
439     }
440 
441     private static class CreateRestrictedProfileTask extends AsyncTask<Void, Void, UserInfo> {
442         private final Context mContext;
443         private final UserManager mUserManager;
444 
CreateRestrictedProfileTask(Context context)445         CreateRestrictedProfileTask(Context context) {
446             mContext = context.getApplicationContext();
447             mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
448         }
449 
450         @Override
doInBackground(Void... params)451         protected UserInfo doInBackground(Void... params) {
452             UserInfo restrictedUserInfo = mUserManager.createProfileForUser(
453                     mContext.getString(R.string.user_new_profile_name),
454                     UserInfo.FLAG_RESTRICTED, UserHandle.myUserId());
455             if (restrictedUserInfo == null) {
456                 final UserInfo existingUserInfo = new RestrictedProfileModel(mContext).getUser();
457                 if (existingUserInfo == null) {
458                     Log.wtf(TAG, "Got back a null user handle!");
459                 }
460                 return existingUserInfo;
461             }
462             int userId = restrictedUserInfo.id;
463             UserHandle user = new UserHandle(userId);
464             mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
465             Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default);
466             mUserManager.setUserIcon(userId, bitmap);
467             // Add shared accounts
468             AccountManager.get(mContext).addSharedAccountsFromParentUser(
469                     UserHandle.of(UserHandle.myUserId()), user);
470             return restrictedUserInfo;
471         }
472 
473         @Override
onPostExecute(UserInfo result)474         protected void onPostExecute(UserInfo result) {
475             sCreateRestrictedProfileTask = null;
476             if (result == null) {
477                 return;
478             }
479             UserSwitchListenerService.updateLaunchPoint(mContext, true);
480             LocalBroadcastManager.getInstance(mContext).sendBroadcast(
481                     new Intent(ACTION_RESTRICTED_PROFILE_CREATED)
482                             .putExtra(EXTRA_RESTRICTED_PROFILE_INFO, result));
483         }
484 
createBitmapFromDrawable(@rawableRes int resId)485         private Bitmap createBitmapFromDrawable(@DrawableRes int resId) {
486             Drawable icon = mContext.getDrawable(resId);
487             if (icon == null) {
488                 throw new IllegalArgumentException("Drawable is missing!");
489             }
490             icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
491             Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
492                     Bitmap.Config.ARGB_8888);
493             icon.draw(new Canvas(bitmap));
494             return bitmap;
495         }
496     }
497 
498     @Override
getMetricsCategory()499     public int getMetricsCategory() {
500         return MetricsProto.MetricsEvent.SECURITY;
501     }
502 }
503