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