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.tv.settings.system;
18 
19 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
20 
21 import android.accounts.AccountManager;
22 import android.annotation.SuppressLint;
23 import android.app.admin.DevicePolicyManager;
24 import android.app.tvsettings.TvSettingsEnums;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.UserInfo;
31 import android.graphics.Bitmap;
32 import android.graphics.Canvas;
33 import android.graphics.drawable.Drawable;
34 import android.os.AsyncTask;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.text.TextUtils;
41 import android.util.Log;
42 
43 import androidx.annotation.DrawableRes;
44 import androidx.annotation.IntDef;
45 import androidx.annotation.Keep;
46 import androidx.fragment.app.Fragment;
47 import androidx.leanback.preference.LeanbackSettingsFragmentCompat;
48 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceGroup;
51 
52 import com.android.tv.settings.R;
53 import com.android.tv.settings.SettingsPreferenceFragment;
54 import com.android.tv.settings.dialog.PinDialogFragment;
55 import com.android.tv.settings.users.AppRestrictionsFragment;
56 import com.android.tv.settings.users.RestrictedProfileModel;
57 import com.android.tv.settings.users.RestrictedProfilePinDialogFragment;
58 import com.android.tv.settings.users.RestrictedProfilePinStorage;
59 import com.android.tv.settings.users.UserSwitchListenerService;
60 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
61 
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.util.List;
65 
66 /**
67  * Base fragment for security settings.
68  */
69 abstract class BaseSecurityFragment extends SettingsPreferenceFragment
70         implements PinDialogFragment.ResultListener {
71 
72     private static final String TAG = "BaseSecurityFragment";
73 
74     protected static final String KEY_UNKNOWN_SOURCES = "unknown_sources";
75     protected static final String KEY_RESTRICTED_PROFILE_GROUP = "restricted_profile_group";
76     protected static final String KEY_RESTRICTED_PROFILE_ENTER = "restricted_profile_enter";
77     protected static final String KEY_RESTRICTED_PROFILE_EXIT = "restricted_profile_exit";
78     protected static final String KEY_RESTRICTED_PROFILE_APPS = "restricted_profile_apps";
79     protected static final String KEY_RESTRICTED_PROFILE_PIN = "restricted_profile_pin";
80     protected static final String KEY_RESTRICTED_PROFILE_CREATE = "restricted_profile_create";
81     protected static final String KEY_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete";
82     protected static final String KEY_RESTRICTED_PROFILE_SKIP = "restricted_profile_skip";
83     protected static final String KEY_MANAGE_DEVICE_ADMIN = "manage_device_admin";
84     protected static final String KEY_ENTERPRISE_PRIVACY = "enterprise_privacy";
85 
86     private static final String ACTION_RESTRICTED_PROFILE_CREATED =
87             "SecurityFragment.RESTRICTED_PROFILE_CREATED";
88     private static final String EXTRA_RESTRICTED_PROFILE_INFO =
89             "SecurityFragment.RESTRICTED_PROFILE_INFO";
90     private static final String SAVESTATE_CREATING_RESTRICTED_PROFILE =
91             "SecurityFragment.CREATING_RESTRICTED_PROFILE";
92 
93     @Retention(RetentionPolicy.SOURCE)
94     @IntDef({PIN_MODE_CHOOSE_LOCKSCREEN,
95             PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT,
96             PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD,
97             PIN_MODE_RESTRICTED_PROFILE_DELETE})
98     private @interface PinMode {}
99     private static final int PIN_MODE_CHOOSE_LOCKSCREEN = 1;
100     private static final int PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT = 2;
101     private static final int PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD = 3;
102     private static final int PIN_MODE_RESTRICTED_PROFILE_DELETE = 4;
103 
104 
105     protected RestrictedProfileModel mRestrictedProfile;
106 
107     private boolean mCreatingRestrictedProfile;
108     private RestrictedProfilePinStorage mRestrictedProfilePinStorage;
109 
110     @SuppressLint("StaticFieldLeak")
111     private static CreateRestrictedProfileTask sCreateRestrictedProfileTask;
112     private final BroadcastReceiver mRestrictedProfileReceiver = new BroadcastReceiver() {
113         @Override
114         public void onReceive(Context context, Intent intent) {
115             UserInfo result = intent.getParcelableExtra(EXTRA_RESTRICTED_PROFILE_INFO);
116             if (isResumed()) {
117                 onRestrictedUserCreated(result);
118             }
119         }
120     };
121 
122     private Handler mUiThreadHandler;
123     private HandlerThread mBackgroundHandlerThread;
124     private Handler mBackgroundHandler;
125 
126     @Override
onCreate(Bundle savedInstanceState)127     public void onCreate(Bundle savedInstanceState) {
128         mRestrictedProfile = new RestrictedProfileModel(getContext());
129 
130         super.onCreate(savedInstanceState);
131         mCreatingRestrictedProfile = savedInstanceState != null
132                 && savedInstanceState.getBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE);
133 
134         mUiThreadHandler = new Handler();
135         mBackgroundHandlerThread = new HandlerThread("SecurityFragmentBackgroundThread");
136         mBackgroundHandlerThread.start();
137         mBackgroundHandler = new Handler(mBackgroundHandlerThread.getLooper());
138     }
139 
140     @Override
onDestroy()141     public void onDestroy() {
142         mBackgroundHandler = null;
143         mBackgroundHandlerThread.quitSafely();
144         mBackgroundHandlerThread = null;
145         mUiThreadHandler = null;
146 
147         super.onDestroy();
148 
149         mRestrictedProfile = null;
150     }
151 
152     @Override
onResume()153     public void onResume() {
154         super.onResume();
155         refresh();
156         LocalBroadcastManager.getInstance(getActivity())
157                 .registerReceiver(mRestrictedProfileReceiver,
158                         new IntentFilter(ACTION_RESTRICTED_PROFILE_CREATED));
159         if (mCreatingRestrictedProfile) {
160             UserInfo userInfo = mRestrictedProfile.getUser();
161             if (userInfo != null) {
162                 onRestrictedUserCreated(userInfo);
163             }
164         }
165     }
166 
167     @Override
onPause()168     public void onPause() {
169         super.onPause();
170         LocalBroadcastManager.getInstance(getActivity())
171                 .unregisterReceiver(mRestrictedProfileReceiver);
172     }
173 
174     @Override
onAttach(Context context)175     public void onAttach(Context context) {
176         super.onAttach(context);
177         mRestrictedProfilePinStorage = RestrictedProfilePinStorage.newInstance(getContext());
178         mRestrictedProfilePinStorage.bind();
179     }
180 
181     @Override
onDetach()182     public void onDetach() {
183         mRestrictedProfilePinStorage.unbind();
184         mRestrictedProfilePinStorage = null;
185         super.onDetach();
186     }
187 
188     @Override
onSaveInstanceState(Bundle outState)189     public void onSaveInstanceState(Bundle outState) {
190         super.onSaveInstanceState(outState);
191         outState.putBoolean(SAVESTATE_CREATING_RESTRICTED_PROFILE, mCreatingRestrictedProfile);
192     }
193 
194 
refresh()195     abstract protected void refresh();
196 
197     @Override
onPreferenceTreeClick(Preference preference)198     public boolean onPreferenceTreeClick(Preference preference) {
199         final String key = preference.getKey();
200         if (TextUtils.isEmpty(key)) {
201             return super.onPreferenceTreeClick(preference);
202         }
203         switch (key) {
204             case KEY_RESTRICTED_PROFILE_ENTER:
205                 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_ENTER_PROFILE);
206                 if (mRestrictedProfile.enterUser()) {
207                     getActivity().finish();
208                 }
209                 return true;
210             case KEY_RESTRICTED_PROFILE_EXIT:
211                 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_EXIT_PROFILE);
212                 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT);
213                 return true;
214             case KEY_RESTRICTED_PROFILE_PIN:
215                 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_PROFILE_CHANGE_PIN);
216                 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD);
217                 return true;
218             case KEY_RESTRICTED_PROFILE_CREATE:
219                 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_CREATE_PROFILE);
220                 createRestrictedProfile();
221                 return true;
222             case KEY_RESTRICTED_PROFILE_DELETE:
223                 logEntrySelected(TvSettingsEnums.APPS_SECURITY_RESTRICTIONS_DELETE_PROFILE);
224                 launchPinDialog(PIN_MODE_RESTRICTED_PROFILE_DELETE);
225                 return true;
226         }
227         return super.onPreferenceTreeClick(preference);
228     }
229 
createRestrictedProfile()230     private void createRestrictedProfile() {
231         mBackgroundHandler.post(() -> {
232             boolean pinIsSet = mRestrictedProfilePinStorage.isPinSet();
233 
234             mUiThreadHandler.post(() -> {
235                 if (pinIsSet) {
236                     addRestrictedUser();
237                 } else {
238                     launchPinDialog(PIN_MODE_CHOOSE_LOCKSCREEN);
239                 }
240             });
241         });
242     }
243 
launchPinDialog(@inMode int pinMode)244     private void launchPinDialog(@PinMode int pinMode) {
245         @PinDialogFragment.PinDialogType
246         int pinDialogMode;
247 
248         switch (pinMode) {
249             case PIN_MODE_CHOOSE_LOCKSCREEN:
250                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN;
251                 break;
252             case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT:
253                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN;
254                 break;
255             case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD:
256                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN;
257                 break;
258             case PIN_MODE_RESTRICTED_PROFILE_DELETE:
259                 pinDialogMode = PinDialogFragment.PIN_DIALOG_TYPE_DELETE_PIN;
260                 break;
261             default:
262                 throw new IllegalArgumentException("Unknown pin mode: " + pinMode);
263         }
264 
265         RestrictedProfilePinDialogFragment restrictedProfilePinDialogFragment =
266                 RestrictedProfilePinDialogFragment.newInstance(pinDialogMode);
267         restrictedProfilePinDialogFragment.setTargetFragment(this, pinMode);
268         restrictedProfilePinDialogFragment.show(getFragmentManager(),
269                 PinDialogFragment.DIALOG_TAG);
270     }
271 
272     @Override
pinFragmentDone(int requestCode, boolean success)273     public void pinFragmentDone(int requestCode, boolean success) {
274         if (!success) {
275             Log.d(TAG, "Request " + requestCode + " unsuccessful.");
276             return;
277         }
278 
279         switch (requestCode) {
280             case PIN_MODE_CHOOSE_LOCKSCREEN:
281                 addRestrictedUser();
282                 break;
283             case PIN_MODE_RESTRICTED_PROFILE_SWITCH_OUT:
284                 mRestrictedProfile.exitUser();
285                 mUiThreadHandler.post(() -> getActivity().finish());
286                 break;
287             case PIN_MODE_RESTRICTED_PROFILE_CHANGE_PASSWORD:
288                 // do nothing
289                 break;
290             case PIN_MODE_RESTRICTED_PROFILE_DELETE:
291                 mUiThreadHandler.post(() -> {
292                     mRestrictedProfile.removeUser();
293                     UserSwitchListenerService.onUserCreatedOrDeleted(getActivity());
294                     refresh();
295                 });
296                 break;
297             default:
298                 Log.d(TAG, "Pin request code not recognised: " + requestCode);
299         }
300     }
301 
addRestrictedUser()302     private void addRestrictedUser() {
303         if (sCreateRestrictedProfileTask == null) {
304             sCreateRestrictedProfileTask = new CreateRestrictedProfileTask(getContext());
305             sCreateRestrictedProfileTask.execute();
306             mCreatingRestrictedProfile = true;
307         }
308         refresh();
309     }
310 
onRestrictedUserCreated(UserInfo result)311     private void onRestrictedUserCreated(UserInfo result) {
312         int userId = result.id;
313         if (result.isRestricted()
314                 && result.restrictedProfileParentId == UserHandle.myUserId()) {
315             final AppRestrictionsFragment restrictionsFragment =
316                     AppRestrictionsFragment.newInstance(userId, true,
317                             shouldExitAfterUpdatingApps());
318             final Fragment settingsFragment = getCallbackFragment();
319             if (settingsFragment instanceof LeanbackSettingsFragmentCompat) {
320                 ((LeanbackSettingsFragmentCompat) settingsFragment)
321                         .startPreferenceFragment(restrictionsFragment);
322             } else if (settingsFragment instanceof TwoPanelSettingsFragment) {
323                 ((TwoPanelSettingsFragment) settingsFragment)
324                         .startPreferenceFragment(restrictionsFragment);
325             } else {
326                 throw new IllegalStateException("Didn't find fragment of expected type: "
327                         + settingsFragment);
328             }
329         }
330         mCreatingRestrictedProfile = false;
331         refresh();
332     }
333 
shouldExitAfterUpdatingApps()334     protected boolean shouldExitAfterUpdatingApps() {
335         return false;
336     }
337 
338     private static class CreateRestrictedProfileTask extends AsyncTask<Void, Void, UserInfo> {
339         private final Context mContext;
340         private final UserManager mUserManager;
341 
CreateRestrictedProfileTask(Context context)342         CreateRestrictedProfileTask(Context context) {
343             mContext = context.getApplicationContext();
344             mUserManager = mContext.getSystemService(UserManager.class);
345         }
346 
347         @Override
doInBackground(Void... params)348         protected UserInfo doInBackground(Void... params) {
349             UserInfo restrictedUserInfo = mUserManager.createProfileForUser(
350                     mContext.getString(R.string.user_new_profile_name),
351                     UserManager.USER_TYPE_FULL_RESTRICTED, /* flags */ 0, UserHandle.myUserId());
352             if (restrictedUserInfo == null) {
353                 final UserInfo existingUserInfo = new RestrictedProfileModel(mContext).getUser();
354                 if (existingUserInfo == null) {
355                     Log.wtf(TAG, "Got back a null user handle!");
356                 }
357                 return existingUserInfo;
358             }
359             int userId = restrictedUserInfo.id;
360             UserHandle user = new UserHandle(userId);
361             mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
362             Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default);
363             mUserManager.setUserIcon(userId, bitmap);
364             // Add shared accounts
365             AccountManager.get(mContext).addSharedAccountsFromParentUser(
366                     UserHandle.of(UserHandle.myUserId()), user);
367             return restrictedUserInfo;
368         }
369 
370         @Override
onPostExecute(UserInfo result)371         protected void onPostExecute(UserInfo result) {
372             sCreateRestrictedProfileTask = null;
373             if (result == null) {
374                 return;
375             }
376             UserSwitchListenerService.onUserCreatedOrDeleted(mContext);
377             LocalBroadcastManager.getInstance(mContext).sendBroadcast(
378                     new Intent(ACTION_RESTRICTED_PROFILE_CREATED)
379                             .putExtra(EXTRA_RESTRICTED_PROFILE_INFO, result));
380         }
381 
createBitmapFromDrawable(@rawableRes int resId)382         private Bitmap createBitmapFromDrawable(@DrawableRes int resId) {
383             Drawable icon = mContext.getDrawable(resId);
384             if (icon == null) {
385                 throw new IllegalArgumentException("Drawable is missing!");
386             }
387             icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
388             Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
389                     Bitmap.Config.ARGB_8888);
390             icon.draw(new Canvas(bitmap));
391             return bitmap;
392         }
393     }
394 
isRestrictedProfileCreationInProgress()395     protected boolean isRestrictedProfileCreationInProgress() {
396         return sCreateRestrictedProfileTask != null;
397     }
398 
399     @Override
getPageId()400     protected int getPageId() {
401         return TvSettingsEnums.APPS_SECURITY_RESTRICTIONS;
402     }
403 }
404