Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2008 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.settings;
     18 
     19 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
     20 
     21 import android.accounts.Account;
     22 import android.accounts.AccountManager;
     23 import android.accounts.AuthenticatorDescription;
     24 import android.app.Activity;
     25 import android.content.ComponentName;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.pm.PackageManager;
     30 import android.content.pm.UserInfo;
     31 import android.content.res.Resources;
     32 import android.graphics.drawable.Drawable;
     33 import android.os.Bundle;
     34 import android.os.Environment;
     35 import android.os.SystemProperties;
     36 import android.os.UserHandle;
     37 import android.os.UserManager;
     38 import android.provider.Settings;
     39 import android.support.annotation.VisibleForTesting;
     40 import android.telephony.euicc.EuiccManager;
     41 import android.text.TextUtils;
     42 import android.util.Log;
     43 import android.view.LayoutInflater;
     44 import android.view.View;
     45 import android.view.View.OnScrollChangeListener;
     46 import android.view.ViewGroup;
     47 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
     48 import android.widget.Button;
     49 import android.widget.CheckBox;
     50 import android.widget.ImageView;
     51 import android.widget.LinearLayout;
     52 import android.widget.ScrollView;
     53 import android.widget.TextView;
     54 
     55 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     56 import com.android.settings.password.ChooseLockSettingsHelper;
     57 import com.android.settings.password.ConfirmLockPattern;
     58 import com.android.settingslib.RestrictedLockUtils;
     59 
     60 import java.util.List;
     61 
     62 /**
     63  * Confirm and execute a reset of the device to a clean "just out of the box"
     64  * state.  Multiple confirmations are required: first, a general "are you sure
     65  * you want to do this?" prompt, followed by a keyguard pattern trace if the user
     66  * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
     67  * ON THE PHONE" prompt.  If at any time the phone is allowed to go to sleep, is
     68  * locked, et cetera, then the confirmation sequence is abandoned.
     69  *
     70  * This is the initial screen.
     71  */
     72 public class MasterClear extends OptionsMenuFragment {
     73     private static final String TAG = "MasterClear";
     74 
     75     private static final int KEYGUARD_REQUEST = 55;
     76 
     77     static final String ERASE_EXTERNAL_EXTRA = "erase_sd";
     78     static final String ERASE_ESIMS_EXTRA = "erase_esim";
     79 
     80     private View mContentView;
     81     private Button mInitiateButton;
     82     private View mExternalStorageContainer;
     83     @VisibleForTesting CheckBox mExternalStorage;
     84     private ScrollView mScrollView;
     85 
     86     private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() {
     87         @Override
     88         public void onGlobalLayout() {
     89             mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
     90             mInitiateButton.setEnabled(hasReachedBottom(mScrollView));
     91         }
     92     };
     93 
     94     /**
     95      * Keyguard validation is run using the standard {@link ConfirmLockPattern}
     96      * component as a subactivity
     97      * @param request the request code to be returned once confirmation finishes
     98      * @return true if confirmation launched
     99      */
    100     private boolean runKeyguardConfirmation(int request) {
    101         Resources res = getActivity().getResources();
    102         return new ChooseLockSettingsHelper(getActivity(), this).launchConfirmationActivity(
    103                 request, res.getText(R.string.master_clear_title));
    104     }
    105 
    106     @Override
    107     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    108         super.onActivityResult(requestCode, resultCode, data);
    109 
    110         if (requestCode != KEYGUARD_REQUEST) {
    111             return;
    112         }
    113 
    114         // If the user entered a valid keyguard trace, present the final
    115         // confirmation prompt; otherwise, go back to the initial state.
    116         if (resultCode == Activity.RESULT_OK) {
    117             showFinalConfirmation();
    118         } else {
    119             establishInitialState();
    120         }
    121     }
    122 
    123     @VisibleForTesting
    124     void showFinalConfirmation() {
    125         Bundle args = new Bundle();
    126         args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
    127         // TODO: Offer the user a choice to wipe eSIMs when it is technically feasible to do so.
    128         args.putBoolean(ERASE_ESIMS_EXTRA, true);
    129         ((SettingsActivity) getActivity()).startPreferencePanel(
    130                 this, MasterClearConfirm.class.getName(),
    131                 args, R.string.master_clear_confirm_title, null, null, 0);
    132     }
    133 
    134     /**
    135      * If the user clicks to begin the reset sequence, we next require a
    136      * keyguard confirmation if the user has currently enabled one.  If there
    137      * is no keyguard available, we simply go to the final confirmation prompt.
    138      *
    139      * If the user is in demo mode, route to the demo mode app for confirmation.
    140      */
    141     @VisibleForTesting
    142     protected final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
    143 
    144         public void onClick(View view) {
    145             final Context context = view.getContext();
    146             if (Utils.isDemoUser(context)) {
    147                 final ComponentName componentName = Utils.getDeviceOwnerComponent(context);
    148                 if (componentName != null) {
    149                     final Intent requestFactoryReset = new Intent()
    150                             .setPackage(componentName.getPackageName())
    151                             .setAction(Intent.ACTION_FACTORY_RESET);
    152                     context.startActivity(requestFactoryReset);
    153                 }
    154             } else if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
    155                 showFinalConfirmation();
    156             }
    157         }
    158     };
    159 
    160     /**
    161      * In its initial state, the activity presents a button for the user to
    162      * click in order to initiate a confirmation sequence.  This method is
    163      * called from various other points in the code to reset the activity to
    164      * this base state.
    165      *
    166      * <p>Reinflating views from resources is expensive and prevents us from
    167      * caching widget pointers, so we use a single-inflate pattern:  we lazy-
    168      * inflate each view, caching all of the widget pointers we'll need at the
    169      * time, then simply reuse the inflated views directly whenever we need
    170      * to change contents.
    171      */
    172     private void establishInitialState() {
    173         mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_master_clear);
    174         mInitiateButton.setOnClickListener(mInitiateListener);
    175         mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container);
    176         mExternalStorage = (CheckBox) mContentView.findViewById(R.id.erase_external);
    177         mScrollView = (ScrollView) mContentView.findViewById(R.id.master_clear_scrollview);
    178 
    179         /*
    180          * If the external storage is emulated, it will be erased with a factory
    181          * reset at any rate. There is no need to have a separate option until
    182          * we have a factory reset that only erases some directories and not
    183          * others. Likewise, if it's non-removable storage, it could potentially have been
    184          * encrypted, and will also need to be wiped.
    185          */
    186         boolean isExtStorageEmulated = Environment.isExternalStorageEmulated();
    187         if (isExtStorageEmulated
    188                 || (!Environment.isExternalStorageRemovable() && isExtStorageEncrypted())) {
    189             mExternalStorageContainer.setVisibility(View.GONE);
    190 
    191             final View externalOption = mContentView.findViewById(R.id.erase_external_option_text);
    192             externalOption.setVisibility(View.GONE);
    193 
    194             final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external);
    195             externalAlsoErased.setVisibility(View.VISIBLE);
    196 
    197             // If it's not emulated, it is on a separate partition but it means we're doing
    198             // a force wipe due to encryption.
    199             mExternalStorage.setChecked(!isExtStorageEmulated);
    200         } else {
    201             mExternalStorageContainer.setOnClickListener(new View.OnClickListener() {
    202 
    203                 @Override
    204                 public void onClick(View v) {
    205                     mExternalStorage.toggle();
    206                 }
    207             });
    208         }
    209 
    210         if (showWipeEuicc()) {
    211             final View esimAlsoErased = mContentView.findViewById(R.id.also_erases_esim);
    212             esimAlsoErased.setVisibility(View.VISIBLE);
    213 
    214             final View noCancelMobilePlan = mContentView.findViewById(R.id.no_cancel_mobile_plan);
    215             noCancelMobilePlan.setVisibility(View.VISIBLE);
    216         }
    217 
    218         final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
    219         loadAccountList(um);
    220         StringBuffer contentDescription = new StringBuffer();
    221         View masterClearContainer = mContentView.findViewById(R.id.master_clear_container);
    222         getContentDescription(masterClearContainer, contentDescription);
    223         masterClearContainer.setContentDescription(contentDescription);
    224 
    225         // Set the status of initiateButton based on scrollview
    226         mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
    227             @Override
    228             public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
    229                 int oldScrollY) {
    230                 if (v instanceof ScrollView && hasReachedBottom((ScrollView) v)) {
    231                     mInitiateButton.setEnabled(true);
    232                 }
    233             }
    234         });
    235 
    236         // Set the initial state of the initiateButton
    237         mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
    238     }
    239 
    240     /**
    241      * Whether to show strings indicating that the eUICC will be wiped.
    242      *
    243      * <p>We show the strings on any device which supports eUICC as long as the eUICC was ever
    244      * provisioned (that is, at least one profile was ever downloaded onto it).
    245      */
    246     @VisibleForTesting
    247     boolean showWipeEuicc() {
    248         Context context = getContext();
    249         if (!isEuiccEnabled(context)) {
    250             return false;
    251         }
    252         ContentResolver cr = context.getContentResolver();
    253         return Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) != 0;
    254     }
    255 
    256     @VisibleForTesting
    257     protected boolean isEuiccEnabled(Context context) {
    258         EuiccManager euiccManager = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
    259         return euiccManager.isEnabled();
    260     }
    261 
    262     @VisibleForTesting
    263     boolean hasReachedBottom(final ScrollView scrollView) {
    264         if (scrollView.getChildCount() < 1) {
    265             return true;
    266         }
    267 
    268         final View view = scrollView.getChildAt(0);
    269         final int diff = view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY());
    270 
    271         return diff <= 0;
    272     }
    273 
    274     private void getContentDescription(View v, StringBuffer description) {
    275        if (v.getVisibility() != View.VISIBLE) {
    276            return;
    277        }
    278        if (v instanceof ViewGroup) {
    279            ViewGroup vGroup = (ViewGroup) v;
    280            for (int i = 0; i < vGroup.getChildCount(); i++) {
    281                View nextChild = vGroup.getChildAt(i);
    282                getContentDescription(nextChild, description);
    283            }
    284        } else if (v instanceof TextView) {
    285            TextView vText = (TextView) v;
    286            description.append(vText.getText());
    287            description.append(","); // Allow Talkback to pause between sections.
    288        }
    289     }
    290 
    291     private boolean isExtStorageEncrypted() {
    292         String state = SystemProperties.get("vold.decrypt");
    293         return !"".equals(state);
    294     }
    295 
    296     private void loadAccountList(final UserManager um) {
    297         View accountsLabel = mContentView.findViewById(R.id.accounts_label);
    298         LinearLayout contents = (LinearLayout)mContentView.findViewById(R.id.accounts);
    299         contents.removeAllViews();
    300 
    301         Context context = getActivity();
    302         final List<UserInfo> profiles = um.getProfiles(UserHandle.myUserId());
    303         final int profilesSize = profiles.size();
    304 
    305         AccountManager mgr = AccountManager.get(context);
    306 
    307         LayoutInflater inflater = (LayoutInflater)context.getSystemService(
    308                 Context.LAYOUT_INFLATER_SERVICE);
    309 
    310         int accountsCount = 0;
    311         for (int profileIndex = 0; profileIndex < profilesSize; profileIndex++) {
    312             final UserInfo userInfo = profiles.get(profileIndex);
    313             final int profileId = userInfo.id;
    314             final UserHandle userHandle = new UserHandle(profileId);
    315             Account[] accounts = mgr.getAccountsAsUser(profileId);
    316             final int N = accounts.length;
    317             if (N == 0) {
    318                 continue;
    319             }
    320             accountsCount += N;
    321 
    322             AuthenticatorDescription[] descs = AccountManager.get(context)
    323                     .getAuthenticatorTypesAsUser(profileId);
    324             final int M = descs.length;
    325 
    326             if (profilesSize > 1) {
    327                 View titleView = Utils.inflateCategoryHeader(inflater, contents);
    328                 final TextView titleText = (TextView) titleView.findViewById(android.R.id.title);
    329                 titleText.setText(userInfo.isManagedProfile() ? R.string.category_work
    330                         : R.string.category_personal);
    331                 contents.addView(titleView);
    332             }
    333 
    334             for (int i = 0; i < N; i++) {
    335                 Account account = accounts[i];
    336                 AuthenticatorDescription desc = null;
    337                 for (int j = 0; j < M; j++) {
    338                     if (account.type.equals(descs[j].type)) {
    339                         desc = descs[j];
    340                         break;
    341                     }
    342                 }
    343                 if (desc == null) {
    344                     Log.w(TAG, "No descriptor for account name=" + account.name
    345                             + " type=" + account.type);
    346                     continue;
    347                 }
    348                 Drawable icon = null;
    349                 try {
    350                     if (desc.iconId != 0) {
    351                         Context authContext = context.createPackageContextAsUser(desc.packageName,
    352                                 0, userHandle);
    353                         icon = context.getPackageManager().getUserBadgedIcon(
    354                                 authContext.getDrawable(desc.iconId), userHandle);
    355                     }
    356                 } catch (PackageManager.NameNotFoundException e) {
    357                     Log.w(TAG, "Bad package name for account type " + desc.type);
    358                 } catch (Resources.NotFoundException e) {
    359                     Log.w(TAG, "Invalid icon id for account type " + desc.type, e);
    360                 }
    361                 if (icon == null) {
    362                     icon = context.getPackageManager().getDefaultActivityIcon();
    363                 }
    364 
    365                 View child = inflater.inflate(R.layout.master_clear_account, contents, false);
    366                 ((ImageView) child.findViewById(android.R.id.icon)).setImageDrawable(icon);
    367                 ((TextView) child.findViewById(android.R.id.title)).setText(account.name);
    368                 contents.addView(child);
    369             }
    370         }
    371 
    372         if (accountsCount > 0) {
    373             accountsLabel.setVisibility(View.VISIBLE);
    374             contents.setVisibility(View.VISIBLE);
    375         }
    376         // Checking for all other users and their profiles if any.
    377         View otherUsers = mContentView.findViewById(R.id.other_users_present);
    378         final boolean hasOtherUsers = (um.getUserCount() - profilesSize) > 0;
    379         otherUsers.setVisibility(hasOtherUsers ? View.VISIBLE : View.GONE);
    380     }
    381 
    382     @Override
    383     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    384             Bundle savedInstanceState) {
    385         final Context context = getContext();
    386         final EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(context,
    387                 UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
    388         final UserManager um = UserManager.get(context);
    389         final boolean disallow = !um.isAdminUser() || RestrictedLockUtils.hasBaseUserRestriction(
    390                 context, UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
    391         if (disallow && !Utils.isDemoUser(context)) {
    392             return inflater.inflate(R.layout.master_clear_disallowed_screen, null);
    393         } else if (admin != null) {
    394             View view = inflater.inflate(R.layout.admin_support_details_empty_view, null);
    395             ShowAdminSupportDetailsDialog.setAdminSupportDetails(getActivity(), view, admin, false);
    396             view.setVisibility(View.VISIBLE);
    397             return view;
    398         }
    399 
    400         mContentView = inflater.inflate(R.layout.master_clear, null);
    401 
    402         establishInitialState();
    403         return mContentView;
    404     }
    405 
    406     @Override
    407     public int getMetricsCategory() {
    408         return MetricsEvent.MASTER_CLEAR;
    409     }
    410 }
    411