1/*
2 * Copyright (C) 2014 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
17package com.android.tv.settings.users;
18
19import com.android.tv.settings.R;
20import com.android.tv.settings.dialog.DialogFragment;
21import com.android.tv.settings.dialog.DialogFragment.Action;
22
23import android.accounts.Account;
24import android.accounts.AccountManager;
25import android.app.Activity;
26import android.app.ActivityManagerNative;
27import android.app.Fragment;
28import android.app.admin.DevicePolicyManager;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.IPackageManager;
33import android.content.pm.UserInfo;
34import android.graphics.Bitmap;
35import android.graphics.Canvas;
36import android.graphics.drawable.Drawable;
37import android.os.AsyncTask;
38import android.os.Bundle;
39import android.os.Handler;
40import android.os.Message;
41import android.os.RemoteException;
42import android.os.ServiceManager;
43import android.os.UserHandle;
44import android.os.UserManager;
45import android.preference.PreferenceManager;
46import android.provider.Settings.Secure;
47import android.util.Log;
48import android.view.inputmethod.InputMethodInfo;
49import android.view.inputmethod.InputMethodManager;
50
51import com.android.internal.widget.ILockSettings;
52import com.android.internal.widget.LockPatternUtils;
53import com.android.tv.dialog.PinDialogFragment;
54
55import java.util.ArrayList;
56import java.util.Collections;
57import java.util.HashMap;
58import java.util.HashSet;
59import java.util.List;
60import java.util.Set;
61
62/**
63 * Activity that allows the configuration of a user's restricted profile.
64 */
65public class RestrictedProfileActivity extends Activity implements Action.Listener,
66        AppLoadingTask.Listener {
67
68    public static class RestrictedProfilePinDialogFragment extends PinDialogFragment {
69
70        private static final String PREF_DISABLE_PIN_UNTIL =
71                "RestrictedProfileActivity$RestrictedProfilePinDialogFragment.disable_pin_until";
72
73        /**
74         * Returns the time until we should disable the PIN dialog (because the user input wrong
75         * PINs repeatedly).
76         */
77        public static final long getDisablePinUntil(Context context) {
78            return PreferenceManager.getDefaultSharedPreferences(context).getLong(
79                    PREF_DISABLE_PIN_UNTIL, 0);
80        }
81
82        /**
83         * Saves the time until we should disable the PIN dialog (because the user input wrong PINs
84         * repeatedly).
85         */
86        public static final void setDisablePinUntil(Context context, long timeMillis) {
87            PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(
88                    PREF_DISABLE_PIN_UNTIL, timeMillis).apply();
89        }
90
91        private final LockPatternUtils mLpu;
92        private final ILockSettings mILockSettings;
93
94        public RestrictedProfilePinDialogFragment(int type, ResultListener listener,
95                LockPatternUtils lpu, ILockSettings iLockSettings) {
96            super(type, listener);
97            mLpu = lpu;
98            mILockSettings = iLockSettings;
99        }
100
101        @Override
102        public long getPinDisabledUntil() {
103            return getDisablePinUntil(getActivity());
104        }
105
106        @Override
107        public void setPinDisabledUntil(long retryDisableTimeout) {
108            setDisablePinUntil(getActivity(), retryDisableTimeout);
109        }
110
111        @Override
112        public void setPin(String pin) {
113            mLpu.saveLockPassword(pin, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
114        }
115
116        @Override
117        public boolean isPinCorrect(String pin) {
118            try {
119                if (mILockSettings.checkPassword(pin, UserHandle.USER_OWNER)) {
120                    return true;
121                }
122            } catch (RemoteException re) {
123                // Do nothing
124            }
125            return false;
126        }
127
128        @Override
129        public boolean isPinSet() {
130            return UserHandle.myUserId() != UserHandle.USER_OWNER || hasLockscreenSecurity(mLpu);
131        }
132    }
133
134    private static final boolean DEBUG = false;
135    private static final String TAG = "RestrictedProfile";
136
137    private static final String
138            ACTION_RESTRICTED_PROFILE_SETUP_LOCKSCREEN = "restricted_setup_locakscreen";
139    private static final String ACTION_RESTRICTED_PROFILE_CREATE = "restricted_profile_create";
140    private static final String
141            ACTION_RESTRICTED_PROFILE_SWITCH_TO = "restricted_profile_switch_to";
142    private static final String
143            ACTION_RESTRICTED_PROFILE_SWITCH_OUT = "restricted_profile_switch_out";
144    private static final String ACTION_RESTRICTED_PROFILE_CONFIG = "restricted_profile_config";
145    private static final String ACTION_RESTRICTED_PROFILE_CONFIG_APPS = "restricted_profile_config_apps";
146    private static final String ACTION_RESTRICTED_PROFILE_CHANGE_PASSWORD = "restricted_profile_change_password";
147    private static final String ACTION_RESTRICTED_PROFILE_DELETE = "restricted_profile_delete";
148    private static final String
149            ACTION_RESTRICTED_PROFILE_DELETE_CONFIRM = "restricted_profile_delete_confirm";
150    private static final String
151            ACTION_RESTRICTED_PROFILE_DELETE_CANCEL = "restricted_profile_delete_cancel";
152
153    /**
154     * The description string that should be used for an action that launches the restricted profile
155     * activity.
156     *
157     * @param context used to get the appropriate string.
158     * @return the description string that should be used for an action that launches the restricted
159     *         profile activity.
160     */
161    public static String getActionDescription(Context context) {
162        return context.getString(isRestrictedProfileInEffect(context) ? R.string.on : R.string.off);
163    }
164
165    public static boolean isRestrictedProfileInEffect(Context context) {
166        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
167        UserInfo restrictedUserInfo = findRestrictedUser(userManager);
168        boolean isOwner = UserHandle.myUserId() == UserHandle.USER_OWNER;
169        boolean isRestrictedProfileOn = restrictedUserInfo != null && !isOwner;
170        return isRestrictedProfileOn;
171    }
172
173    static void switchUserNow(int userId) {
174        try {
175            ActivityManagerNative.getDefault().switchUser(userId);
176        } catch (RemoteException re) {
177            Log.e(TAG, "Caught exception while switching user! " + re);
178        }
179    }
180
181    static int getIconResource() {
182        return R.drawable.ic_settings_restricted_profile;
183    }
184
185    static UserInfo findRestrictedUser(UserManager userManager) {
186        for (UserInfo userInfo : userManager.getUsers()) {
187            if (userInfo.isRestricted()) {
188                return userInfo;
189            }
190        }
191        return null;
192    }
193
194    private final HashMap<String, Boolean> mSelectedPackages = new HashMap<String, Boolean>();
195    private final boolean mIsOwner = UserHandle.myUserId() == UserHandle.USER_OWNER;
196    private final AsyncTask<Void, Void, UserInfo>
197            mAddUserAsyncTask = new AsyncTask<Void, Void, UserInfo>() {
198        @Override
199        protected UserInfo doInBackground(Void... params) {
200            UserInfo restrictedUserInfo = mUserManager.createUser(
201                    RestrictedProfileActivity.this.getString(R.string.user_new_profile_name),
202                    UserInfo.FLAG_RESTRICTED);
203            if (restrictedUserInfo == null) {
204                Log.wtf(TAG, "Got back a null user handle!");
205                return null;
206            }
207            int userId = restrictedUserInfo.id;
208            UserHandle user = new UserHandle(userId);
209            mUserManager.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
210            Secure.putIntForUser(getContentResolver(), Secure.LOCATION_MODE,
211                    Secure.LOCATION_MODE_OFF, userId);
212            mUserManager.setUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, true, user);
213            Bitmap bitmap = createBitmapFromDrawable(R.drawable.ic_avatar_default);
214            mUserManager.setUserIcon(userId, bitmap);
215            // Add shared accounts
216            AccountManager am = AccountManager.get(RestrictedProfileActivity.this);
217            Account[] accounts = am.getAccounts();
218            if (accounts != null) {
219                for (Account account : accounts) {
220                    am.addSharedAccount(account, user);
221                }
222            }
223            return restrictedUserInfo;
224        }
225
226        @Override
227        protected void onPostExecute(UserInfo result) {
228            if (result == null) {
229                return;
230            }
231            mRestrictedUserInfo = result;
232            UserSwitchListenerService.updateLaunchPoint(RestrictedProfileActivity.this, true);
233            int userId = result.id;
234            if (result.isRestricted() && mIsOwner) {
235                DialogFragment dialogFragment = UserAppRestrictionsDialogFragment.newInstance(
236                        RestrictedProfileActivity.this, userId, true);
237                DialogFragment.add(getFragmentManager(), dialogFragment);
238                mMainMenuDialogFragment.setActions(getMainMenuActions());
239            }
240        }
241    };
242
243    private UserManager mUserManager;
244    private UserInfo mRestrictedUserInfo;
245    private DialogFragment mMainMenuDialogFragment;
246    private ILockSettings mLockSettingsService;
247    private Handler mHandler;
248    private IPackageManager mIPm;
249    private AppLoadingTask mAppLoadingTask;
250    private Action mConfigAppsAction;
251    private DialogFragment mConfigDialogFragment;
252
253    @Override
254    protected void onCreate(Bundle savedInstanceState) {
255        super.onCreate(savedInstanceState);
256        mHandler = new Handler();
257        mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
258        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
259        mRestrictedUserInfo = findRestrictedUser(mUserManager);
260        mConfigAppsAction = createConfigAppsAction(-1);
261        mMainMenuDialogFragment = new DialogFragment.Builder()
262                .title(getString(R.string.launcher_restricted_profile_app_name))
263                .description(getString(R.string.user_add_profile_item_summary))
264                .iconResourceId(getIconResource())
265                .iconBackgroundColor(getResources().getColor(R.color.icon_background))
266                .actions(getMainMenuActions()).build();
267        DialogFragment.add(getFragmentManager(), mMainMenuDialogFragment);
268    }
269
270    @Override
271    protected void onResume() {
272        super.onResume();
273        if (mRestrictedUserInfo != null && (mAppLoadingTask == null
274                || mAppLoadingTask.getStatus() == AsyncTask.Status.FINISHED)) {
275            mAppLoadingTask = new AppLoadingTask(this, mRestrictedUserInfo.id, false, mIPm, this);
276            mAppLoadingTask.execute((Void[]) null);
277        }
278    }
279
280    @Override
281    public void onPackageEnableChanged(String packageName, boolean enabled) {
282    }
283
284    @Override
285    public void onActionsLoaded(ArrayList<Action> actions) {
286        int allowedApps = 0;
287        for(Action action : actions) {
288            if(action.isChecked()) {
289                allowedApps++;
290            }
291        }
292        mConfigAppsAction = createConfigAppsAction(allowedApps);
293        if (mConfigDialogFragment != null) {
294            mConfigDialogFragment.setActions(getConfigActions());
295        }
296    }
297
298    @Override
299    public void onActionClicked(Action action) {
300        if (ACTION_RESTRICTED_PROFILE_SWITCH_TO.equals(action.getKey())) {
301            switchUserNow(mRestrictedUserInfo.id);
302            finish();
303        } else if (ACTION_RESTRICTED_PROFILE_SWITCH_OUT.equals(action.getKey())) {
304            if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) {
305                return;
306            }
307            new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN,
308                    new PinDialogFragment.ResultListener() {
309                        @Override
310                        public void done(boolean success) {
311                            if (success) {
312                                switchUserNow(UserHandle.USER_OWNER);
313                                finish();
314                            }
315                        }
316                    }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(),
317                    PinDialogFragment.DIALOG_TAG);
318        } else if (ACTION_RESTRICTED_PROFILE_CHANGE_PASSWORD.equals(action.getKey())) {
319            if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) {
320                return;
321            }
322            new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN,
323                    new PinDialogFragment.ResultListener() {
324                        @Override
325                        public void done(boolean success) {
326                            // do nothing
327                        }
328                    }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(),
329                    PinDialogFragment.DIALOG_TAG);
330        } else if (ACTION_RESTRICTED_PROFILE_CONFIG.equals(action.getKey())) {
331            mConfigDialogFragment = new DialogFragment.Builder()
332                    .title(getString(R.string.restricted_profile_configure_title))
333                    .iconResourceId(getIconResource())
334                    .iconBackgroundColor(getResources().getColor(R.color.icon_background))
335                    .actions(getConfigActions()).build();
336            DialogFragment.add(getFragmentManager(), mConfigDialogFragment);
337        } else if (ACTION_RESTRICTED_PROFILE_CONFIG_APPS.equals(action.getKey())) {
338            DialogFragment dialogFragment = UserAppRestrictionsDialogFragment.newInstance(
339                    RestrictedProfileActivity.this, mRestrictedUserInfo.id, false);
340            DialogFragment.add(getFragmentManager(), dialogFragment);
341        } else if (ACTION_RESTRICTED_PROFILE_DELETE.equals(action.getKey())) {
342            if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) {
343                return;
344            }
345            new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN,
346                    new PinDialogFragment.ResultListener() {
347                        @Override
348                        public void done(boolean success) {
349                            if (success) {
350                                removeRestrictedUser();
351                                LockPatternUtils lpu = new LockPatternUtils(
352                                        RestrictedProfileActivity.this);
353                                lpu.clearLock(false);
354                            }
355                        }
356                    }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(),
357                    PinDialogFragment.DIALOG_TAG);
358        } else if (ACTION_RESTRICTED_PROFILE_DELETE_CONFIRM.equals(action.getKey())) {
359            // TODO remove once we confirm it's not needed
360            removeRestrictedUser();
361            LockPatternUtils lpu = new LockPatternUtils(this);
362            lpu.clearLock(false);
363        } else if (ACTION_RESTRICTED_PROFILE_DELETE_CANCEL.equals(action.getKey())) {
364            // TODO remove once we confirm it's not needed
365            onBackPressed();
366        } else if (ACTION_RESTRICTED_PROFILE_CREATE.equals(action.getKey())) {
367            if (hasLockscreenSecurity(new LockPatternUtils(this))) {
368                addRestrictedUser();
369            } else {
370                launchChooseLockscreen();
371            }
372        }
373    }
374
375    private ILockSettings getLockSettings() {
376        if (mLockSettingsService == null) {
377            mLockSettingsService = ILockSettings.Stub.asInterface(
378                    ServiceManager.getService("lock_settings"));
379        }
380        return mLockSettingsService;
381    }
382
383    private ArrayList<Action> getMainMenuActions() {
384        ArrayList<Action> actions = new ArrayList<Action>();
385        if (mRestrictedUserInfo != null) {
386            if (mIsOwner) {
387                actions.add(new Action.Builder()
388                        .key(ACTION_RESTRICTED_PROFILE_SWITCH_TO)
389                        .title(getString(R.string.restricted_profile_switch_to))
390                        .build());
391                actions.add(new Action.Builder()
392                        .key(ACTION_RESTRICTED_PROFILE_CONFIG)
393                        .title(getString(R.string.restricted_profile_configure_title))
394                        .build());
395                actions.add(new Action.Builder()
396                        .key(ACTION_RESTRICTED_PROFILE_DELETE)
397                        .title(getString(R.string.restricted_profile_delete_title))
398                        .build());
399            } else {
400                actions.add(new Action.Builder()
401                        .key(ACTION_RESTRICTED_PROFILE_SWITCH_OUT)
402                        .title(getString(R.string.restricted_profile_switch_out))
403                        .build());
404            }
405        } else {
406            actions.add(new Action.Builder()
407                    .key(ACTION_RESTRICTED_PROFILE_CREATE)
408                        .title(getString(R.string.restricted_profile_configure_title))
409                    .build());
410        }
411        return actions;
412    }
413
414    private ArrayList<Action> getConfigActions() {
415        ArrayList<Action> actions = new ArrayList<Action>();
416        actions.add(new Action.Builder()
417                .key(ACTION_RESTRICTED_PROFILE_CHANGE_PASSWORD)
418                .title(getString(R.string.restricted_profile_change_password_title))
419                .build());
420        actions.add(mConfigAppsAction);
421        return actions;
422    }
423
424    private Action createConfigAppsAction(int allowedApps) {
425        String description = allowedApps >= 0 ? getResources().getQuantityString(
426                R.plurals.restricted_profile_configure_apps_description, allowedApps, allowedApps)
427                : getString(R.string.restricted_profile_configure_apps_description_loading);
428        return new Action.Builder()
429                .key(ACTION_RESTRICTED_PROFILE_CONFIG_APPS)
430                .title(getString(R.string.restricted_profile_configure_apps_title))
431                .description(description)
432                .build();
433    }
434
435    private static boolean hasLockscreenSecurity(LockPatternUtils lpu) {
436        return lpu.isLockPasswordEnabled() || lpu.isLockPatternEnabled();
437    }
438
439    private void launchChooseLockscreen() {
440        if (getFragmentManager().findFragmentByTag(PinDialogFragment.DIALOG_TAG) != null) {
441            return;
442        }
443        new RestrictedProfilePinDialogFragment(PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN,
444                new PinDialogFragment.ResultListener() {
445                    @Override
446                    public void done(boolean success) {
447                        if (success) {
448                            addRestrictedUser();
449                        }
450                    }
451                }, new LockPatternUtils(this), getLockSettings()).show(getFragmentManager(),
452                PinDialogFragment.DIALOG_TAG);
453    }
454
455    private void removeRestrictedUser() {
456        mHandler.post(new Runnable() {
457            @Override
458            public void run() {
459                mUserManager.removeUser(mRestrictedUserInfo.id);
460                // pop confirm dialog
461                mRestrictedUserInfo = null;
462                UserSwitchListenerService.updateLaunchPoint(RestrictedProfileActivity.this, false);
463                mMainMenuDialogFragment.setActions(getMainMenuActions());
464                getFragmentManager().popBackStack();
465            }
466        });
467    }
468
469    private Bitmap createBitmapFromDrawable(int resId) {
470        Drawable icon = getResources().getDrawable(resId);
471        icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
472        Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
473                Bitmap.Config.ARGB_8888);
474        icon.draw(new Canvas(bitmap));
475        return bitmap;
476    }
477
478    private void addRestrictedUser() {
479        if (AsyncTask.Status.PENDING == mAddUserAsyncTask.getStatus()) {
480            mAddUserAsyncTask.execute((Void[]) null);
481        }
482    }
483}
484