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