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.security;
18 
19 import android.app.Activity;
20 import android.app.admin.DevicePolicyManager;
21 import android.app.settings.SettingsEnums;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.res.Resources;
27 import android.os.BatteryManager;
28 import android.os.Bundle;
29 import android.os.UserHandle;
30 import android.os.storage.StorageManager;
31 import android.text.TextUtils;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.Button;
36 
37 import androidx.appcompat.app.AlertDialog;
38 import androidx.preference.Preference;
39 
40 import com.android.internal.widget.LockscreenCredential;
41 import com.android.settings.CryptKeeperConfirm;
42 import com.android.settings.R;
43 import com.android.settings.SettingsActivity;
44 import com.android.settings.core.InstrumentedPreferenceFragment;
45 import com.android.settings.password.ChooseLockSettingsHelper;
46 import com.android.settings.password.ConfirmLockPattern;
47 
48 public class CryptKeeperSettings extends InstrumentedPreferenceFragment {
49     private static final String TAG = "CryptKeeper";
50     private static final String TYPE = "type";
51     private static final String PASSWORD = "password";
52 
53     private static final int KEYGUARD_REQUEST = 55;
54 
55     // Minimum battery charge level (in percent) to launch encryption.  If the battery charge is
56     // lower than this, encryption should not be activated.
57     private static final int MIN_BATTERY_LEVEL = 80;
58 
59     private View mContentView;
60     private Button mInitiateButton;
61     private View mPowerWarning;
62     private View mBatteryWarning;
63     private IntentFilter mIntentFilter;
64 
65     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
66         @Override
67         public void onReceive(Context context, Intent intent) {
68             String action = intent.getAction();
69             if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
70                 final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
71                 final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
72                 final int invalidCharger = intent.getIntExtra(
73                     BatteryManager.EXTRA_INVALID_CHARGER, 0);
74 
75                 final boolean levelOk = level >= MIN_BATTERY_LEVEL;
76                 final boolean pluggedOk =
77                     ((plugged & BatteryManager.BATTERY_PLUGGED_ANY) != 0) &&
78                      invalidCharger == 0;
79 
80                 // Update UI elements based on power/battery status
81                 mInitiateButton.setEnabled(levelOk && pluggedOk);
82                 mPowerWarning.setVisibility(pluggedOk ? View.GONE : View.VISIBLE );
83                 mBatteryWarning.setVisibility(levelOk ? View.GONE : View.VISIBLE);
84             }
85         }
86     };
87 
88     /**
89      * If the user clicks to begin the reset sequence, we next require a
90      * keyguard confirmation if the user has currently enabled one.  If there
91      * is no keyguard available, we prompt the user to set a password.
92      */
93     private Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
94         @Override
95         public void onClick(View v) {
96             if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
97                 // TODO replace (or follow) this dialog with an explicit launch into password UI
98                 new AlertDialog.Builder(getActivity())
99                     .setTitle(R.string.crypt_keeper_dialog_need_password_title)
100                     .setMessage(R.string.crypt_keeper_dialog_need_password_message)
101                     .setPositiveButton(android.R.string.ok, null)
102                     .create()
103                     .show();
104             }
105         }
106     };
107 
108     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)109     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
110         mContentView = inflater.inflate(R.layout.crypt_keeper_settings, null);
111 
112         mIntentFilter = new IntentFilter();
113         mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
114 
115         mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_encrypt);
116         mInitiateButton.setOnClickListener(mInitiateListener);
117         mInitiateButton.setEnabled(false);
118 
119         mPowerWarning = mContentView.findViewById(R.id.warning_unplugged);
120         mBatteryWarning = mContentView.findViewById(R.id.warning_low_charge);
121 
122         return mContentView;
123     }
124 
125     @Override
getMetricsCategory()126     public int getMetricsCategory() {
127         return SettingsEnums.CRYPT_KEEPER;
128     }
129 
130     @Override
onResume()131     public void onResume() {
132         super.onResume();
133         getActivity().registerReceiver(mIntentReceiver, mIntentFilter);
134     }
135 
136     @Override
onPause()137     public void onPause() {
138         super.onPause();
139         getActivity().unregisterReceiver(mIntentReceiver);
140     }
141 
142     /**
143      * If encryption is already started, and this launched via a "start encryption" intent,
144      * then exit immediately - it's already up and running, so there's no point in "starting" it.
145      */
146     @Override
onActivityCreated(Bundle savedInstanceState)147     public void onActivityCreated(Bundle savedInstanceState) {
148         super.onActivityCreated(savedInstanceState);
149         Activity activity = getActivity();
150         Intent intent = activity.getIntent();
151         if (DevicePolicyManager.ACTION_START_ENCRYPTION.equals(intent.getAction())) {
152             DevicePolicyManager dpm = (DevicePolicyManager)
153                     activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
154             if (dpm != null) {
155                 int status = dpm.getStorageEncryptionStatus();
156                 if (status != DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE) {
157                     // There is nothing to do here, so simply finish() (which returns to caller)
158                     activity.finish();
159                 }
160             }
161         }
162         activity.setTitle(R.string.crypt_keeper_encrypt_title);
163     }
164 
165     /**
166      * Keyguard validation is run using the standard {@link ConfirmLockPattern}
167      * component as a subactivity
168      * @param request the request code to be returned once confirmation finishes
169      * @return true if confirmation launched
170      */
runKeyguardConfirmation(int request)171     private boolean runKeyguardConfirmation(int request) {
172         Resources res = getActivity().getResources();
173         ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this);
174 
175         if (helper.utils().getKeyguardStoredPasswordQuality(UserHandle.myUserId())
176                 == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
177             showFinalConfirmation(StorageManager.CRYPT_TYPE_DEFAULT, "".getBytes());
178             return true;
179         }
180 
181         return helper.launchConfirmationActivity(request,
182                 res.getText(R.string.crypt_keeper_encrypt_title), true);
183     }
184 
185     @Override
onActivityResult(int requestCode, int resultCode, Intent data)186     public void onActivityResult(int requestCode, int resultCode, Intent data) {
187         super.onActivityResult(requestCode, resultCode, data);
188 
189         if (requestCode != KEYGUARD_REQUEST) {
190             return;
191         }
192 
193         // If the user entered a valid keyguard trace, present the final
194         // confirmation prompt; otherwise, go back to the initial state.
195         if (resultCode == Activity.RESULT_OK && data != null) {
196             int type = data.getIntExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, -1);
197             LockscreenCredential password = data.getParcelableExtra(
198                     ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
199             if (password != null && !password.isNone()) {
200                 showFinalConfirmation(type, password.getCredential());
201             }
202         }
203     }
204 
showFinalConfirmation(int type, byte[] password)205     private void showFinalConfirmation(int type, byte[] password) {
206         Preference preference = new Preference(getPreferenceManager().getContext());
207         preference.setFragment(CryptKeeperConfirm.class.getName());
208         preference.setTitle(R.string.crypt_keeper_confirm_title);
209         addEncryptionInfoToPreference(preference, type, password);
210         ((SettingsActivity) getActivity()).onPreferenceStartFragment(null, preference);
211     }
212 
addEncryptionInfoToPreference(Preference preference, int type, byte[] password)213     private void addEncryptionInfoToPreference(Preference preference, int type, byte[] password) {
214         Activity activity = getActivity();
215         DevicePolicyManager dpm = (DevicePolicyManager)
216                 activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
217         if (dpm.getDoNotAskCredentialsOnBoot()) {
218             preference.getExtras().putInt(TYPE, StorageManager.CRYPT_TYPE_DEFAULT);
219             preference.getExtras().putByteArray(PASSWORD, "".getBytes());
220         } else {
221             preference.getExtras().putInt(TYPE, type);
222             preference.getExtras().putByteArray(PASSWORD, password);
223         }
224     }
225 }
226