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