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.internal.policy.impl;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.res.Configuration;
24import android.database.ContentObserver;
25import static android.os.BatteryManager.BATTERY_STATUS_CHARGING;
26import static android.os.BatteryManager.BATTERY_STATUS_FULL;
27import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
28import android.media.AudioManager;
29import android.os.BatteryManager;
30import android.os.Handler;
31import android.os.Message;
32import android.os.SystemClock;
33import android.provider.Settings;
34import android.provider.Telephony;
35import static android.provider.Telephony.Intents.EXTRA_PLMN;
36import static android.provider.Telephony.Intents.EXTRA_SHOW_PLMN;
37import static android.provider.Telephony.Intents.EXTRA_SHOW_SPN;
38import static android.provider.Telephony.Intents.EXTRA_SPN;
39import static android.provider.Telephony.Intents.SPN_STRINGS_UPDATED_ACTION;
40
41import com.android.internal.telephony.IccCard;
42import com.android.internal.telephony.TelephonyIntents;
43
44import android.telephony.TelephonyManager;
45import android.util.Log;
46import com.android.internal.R;
47import com.google.android.collect.Lists;
48
49import java.util.ArrayList;
50
51/**
52 * Watches for updates that may be interesting to the keyguard, and provides
53 * the up to date information as well as a registration for callbacks that care
54 * to be updated.
55 *
56 * Note: under time crunch, this has been extended to include some stuff that
57 * doesn't really belong here.  see {@link #handleBatteryUpdate} where it shutdowns
58 * the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()}
59 * and {@link #clearFailedAttempts()}.  Maybe we should rename this 'KeyguardContext'...
60 */
61public class KeyguardUpdateMonitor {
62
63    static private final String TAG = "KeyguardUpdateMonitor";
64    static private final boolean DEBUG = false;
65
66    private static final int LOW_BATTERY_THRESHOLD = 20;
67
68    private final Context mContext;
69
70    private IccCard.State mSimState = IccCard.State.READY;
71
72    private boolean mKeyguardBypassEnabled;
73
74    private boolean mDeviceProvisioned;
75
76    private int mBatteryLevel;
77
78    private int mBatteryStatus;
79
80    private CharSequence mTelephonyPlmn;
81    private CharSequence mTelephonySpn;
82
83    private int mFailedAttempts = 0;
84
85    private Handler mHandler;
86
87    private ArrayList<InfoCallback> mInfoCallbacks = Lists.newArrayList();
88    private ArrayList<SimStateCallback> mSimStateCallbacks = Lists.newArrayList();
89    private ContentObserver mContentObserver;
90
91    // messages for the handler
92    private static final int MSG_TIME_UPDATE = 301;
93    private static final int MSG_BATTERY_UPDATE = 302;
94    private static final int MSG_CARRIER_INFO_UPDATE = 303;
95    private static final int MSG_SIM_STATE_CHANGE = 304;
96    private static final int MSG_RINGER_MODE_CHANGED = 305;
97    private static final int MSG_PHONE_STATE_CHANGED = 306;
98
99
100    /**
101     * When we receive a
102     * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
103     * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
104     * we need a single object to pass to the handler.  This class helps decode
105     * the intent and provide a {@link SimCard.State} result.
106     */
107    private static class SimArgs {
108
109        public final IccCard.State simState;
110
111        private SimArgs(Intent intent) {
112            if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
113                throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
114            }
115            String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE);
116            if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
117                this.simState = IccCard.State.ABSENT;
118            } else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
119                this.simState = IccCard.State.READY;
120            } else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
121                final String lockedReason = intent
122                        .getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
123                if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
124                    this.simState = IccCard.State.PIN_REQUIRED;
125                } else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
126                    this.simState = IccCard.State.PUK_REQUIRED;
127                } else {
128                    this.simState = IccCard.State.UNKNOWN;
129                }
130            } else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
131                this.simState = IccCard.State.NETWORK_LOCKED;
132            } else {
133                this.simState = IccCard.State.UNKNOWN;
134            }
135        }
136
137        public String toString() {
138            return simState.toString();
139        }
140    }
141
142    public KeyguardUpdateMonitor(Context context) {
143        mContext = context;
144
145        mHandler = new Handler() {
146            @Override
147            public void handleMessage(Message msg) {
148                switch (msg.what) {
149                    case MSG_TIME_UPDATE:
150                        handleTimeUpdate();
151                        break;
152                    case MSG_BATTERY_UPDATE:
153                        handleBatteryUpdate(msg.arg1,  msg.arg2);
154                        break;
155                    case MSG_CARRIER_INFO_UPDATE:
156                        handleCarrierInfoUpdate();
157                        break;
158                    case MSG_SIM_STATE_CHANGE:
159                        handleSimStateChange((SimArgs) msg.obj);
160                        break;
161                    case MSG_RINGER_MODE_CHANGED:
162                        handleRingerModeChange(msg.arg1);
163                        break;
164                    case MSG_PHONE_STATE_CHANGED:
165                        handlePhoneStateChanged((String)msg.obj);
166                        break;
167                }
168            }
169        };
170
171        mKeyguardBypassEnabled = context.getResources().getBoolean(
172                com.android.internal.R.bool.config_bypass_keyguard_if_slider_open);
173
174        mDeviceProvisioned = Settings.Secure.getInt(
175                mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
176
177        // Since device can't be un-provisioned, we only need to register a content observer
178        // to update mDeviceProvisioned when we are...
179        if (!mDeviceProvisioned) {
180            mContentObserver = new ContentObserver(mHandler) {
181                @Override
182                public void onChange(boolean selfChange) {
183                    super.onChange(selfChange);
184                    mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(),
185                        Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
186                    if (mDeviceProvisioned && mContentObserver != null) {
187                        // We don't need the observer anymore...
188                        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
189                        mContentObserver = null;
190                    }
191                    if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
192                }
193            };
194
195            mContext.getContentResolver().registerContentObserver(
196                    Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
197                    false, mContentObserver);
198
199            // prevent a race condition between where we check the flag and where we register the
200            // observer by grabbing the value once again...
201            mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(),
202                Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
203        }
204
205        // take a guess to start
206        mSimState = IccCard.State.READY;
207        mBatteryStatus = BATTERY_STATUS_FULL;
208        mBatteryLevel = 100;
209
210        mTelephonyPlmn = getDefaultPlmn();
211
212        // setup receiver
213        final IntentFilter filter = new IntentFilter();
214        filter.addAction(Intent.ACTION_TIME_TICK);
215        filter.addAction(Intent.ACTION_TIME_CHANGED);
216        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
217        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
218        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
219        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
220        filter.addAction(SPN_STRINGS_UPDATED_ACTION);
221        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
222        context.registerReceiver(new BroadcastReceiver() {
223
224            public void onReceive(Context context, Intent intent) {
225                final String action = intent.getAction();
226                if (DEBUG) Log.d(TAG, "received broadcast " + action);
227
228                if (Intent.ACTION_TIME_TICK.equals(action)
229                        || Intent.ACTION_TIME_CHANGED.equals(action)
230                        || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
231                    mHandler.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE));
232                } else if (SPN_STRINGS_UPDATED_ACTION.equals(action)) {
233                    mTelephonyPlmn = getTelephonyPlmnFrom(intent);
234                    mTelephonySpn = getTelephonySpnFrom(intent);
235                    mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_UPDATE));
236                } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
237                    final int pluggedInStatus = intent
238                            .getIntExtra("status", BATTERY_STATUS_UNKNOWN);
239                    int batteryLevel = intent.getIntExtra("level", 0);
240                    final Message msg = mHandler.obtainMessage(
241                            MSG_BATTERY_UPDATE,
242                            pluggedInStatus,
243                            batteryLevel);
244                    mHandler.sendMessage(msg);
245                } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
246                    mHandler.sendMessage(mHandler.obtainMessage(
247                            MSG_SIM_STATE_CHANGE,
248                            new SimArgs(intent)));
249                } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
250                    mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED,
251                            intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
252                } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
253                    String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
254                    mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
255                }
256            }
257        }, filter);
258    }
259
260    protected void handlePhoneStateChanged(String newState) {
261        if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
262        for (int i = 0; i < mInfoCallbacks.size(); i++) {
263            mInfoCallbacks.get(i).onPhoneStateChanged(newState);
264        }
265    }
266
267    protected void handleRingerModeChange(int mode) {
268        if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
269        for (int i = 0; i < mInfoCallbacks.size(); i++) {
270            mInfoCallbacks.get(i).onRingerModeChanged(mode);
271        }
272    }
273
274    /**
275     * Handle {@link #MSG_TIME_UPDATE}
276     */
277    private void handleTimeUpdate() {
278        if (DEBUG) Log.d(TAG, "handleTimeUpdate");
279        for (int i = 0; i < mInfoCallbacks.size(); i++) {
280            mInfoCallbacks.get(i).onTimeChanged();
281        }
282    }
283
284    /**
285     * Handle {@link #MSG_BATTERY_UPDATE}
286     */
287    private void handleBatteryUpdate(int batteryStatus, int batteryLevel) {
288        if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
289        if (isBatteryUpdateInteresting(batteryStatus, batteryLevel)) {
290            mBatteryStatus = batteryStatus;
291            mBatteryLevel = batteryLevel;
292            final boolean pluggedIn = isPluggedIn(batteryStatus);;
293            for (int i = 0; i < mInfoCallbacks.size(); i++) {
294                mInfoCallbacks.get(i).onRefreshBatteryInfo(
295                        shouldShowBatteryInfo(), pluggedIn, batteryLevel);
296            }
297        }
298    }
299
300    /**
301     * Handle {@link #MSG_CARRIER_INFO_UPDATE}
302     */
303    private void handleCarrierInfoUpdate() {
304        if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
305            + ", spn = " + mTelephonySpn);
306
307        for (int i = 0; i < mInfoCallbacks.size(); i++) {
308            mInfoCallbacks.get(i).onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
309        }
310    }
311
312    /**
313     * Handle {@link #MSG_SIM_STATE_CHANGE}
314     */
315    private void handleSimStateChange(SimArgs simArgs) {
316        final IccCard.State state = simArgs.simState;
317
318        if (DEBUG) {
319            Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
320                    + "state resolved to " + state.toString());
321        }
322
323        if (state != IccCard.State.UNKNOWN && state != mSimState) {
324            mSimState = state;
325            for (int i = 0; i < mSimStateCallbacks.size(); i++) {
326                mSimStateCallbacks.get(i).onSimStateChanged(state);
327            }
328        }
329    }
330
331    /**
332     * @param status One of the statuses of {@link android.os.BatteryManager}
333     * @return Whether the status maps to a status for being plugged in.
334     */
335    private boolean isPluggedIn(int status) {
336        return status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL;
337    }
338
339    private boolean isBatteryUpdateInteresting(int batteryStatus, int batteryLevel) {
340        // change in plug is always interesting
341        final boolean isPluggedIn = isPluggedIn(batteryStatus);
342        final boolean wasPluggedIn = isPluggedIn(mBatteryStatus);
343        final boolean stateChangedWhilePluggedIn =
344            wasPluggedIn == true && isPluggedIn == true && (mBatteryStatus != batteryStatus);
345        if (wasPluggedIn != isPluggedIn || stateChangedWhilePluggedIn) {
346            return true;
347        }
348
349        // change in battery level while plugged in
350        if (isPluggedIn && mBatteryLevel != batteryLevel) {
351            return true;
352        }
353
354        if (!isPluggedIn) {
355            // not plugged in and below threshold
356            if (isBatteryLow(batteryLevel) && batteryLevel != mBatteryLevel) {
357                return true;
358            }
359        }
360        return false;
361    }
362
363    private boolean isBatteryLow(int batteryLevel) {
364        return batteryLevel < LOW_BATTERY_THRESHOLD;
365    }
366
367    /**
368     * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
369     * @return The string to use for the plmn, or null if it should not be shown.
370     */
371    private CharSequence getTelephonyPlmnFrom(Intent intent) {
372        if (intent.getBooleanExtra(EXTRA_SHOW_PLMN, false)) {
373            final String plmn = intent.getStringExtra(EXTRA_PLMN);
374            if (plmn != null) {
375                return plmn;
376            } else {
377                return getDefaultPlmn();
378            }
379        }
380        return null;
381    }
382
383    /**
384     * @return The default plmn (no service)
385     */
386    private CharSequence getDefaultPlmn() {
387        return mContext.getResources().getText(
388                        R.string.lockscreen_carrier_default);
389    }
390
391    /**
392     * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
393     * @return The string to use for the plmn, or null if it should not be shown.
394     */
395    private CharSequence getTelephonySpnFrom(Intent intent) {
396        if (intent.getBooleanExtra(EXTRA_SHOW_SPN, false)) {
397            final String spn = intent.getStringExtra(EXTRA_SPN);
398            if (spn != null) {
399                return spn;
400            }
401        }
402        return null;
403    }
404
405    /**
406     * Remove the given observer from being registered from any of the kinds
407     * of callbacks.
408     * @param observer The observer to remove (an instance of {@link ConfigurationChangeCallback},
409     *   {@link InfoCallback} or {@link SimStateCallback}
410     */
411    public void removeCallback(Object observer) {
412        mInfoCallbacks.remove(observer);
413        mSimStateCallbacks.remove(observer);
414    }
415
416    /**
417     * Callback for general information relevant to lock screen.
418     */
419    interface InfoCallback {
420        void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);
421        void onTimeChanged();
422
423        /**
424         * @param plmn The operator name of the registered network.  May be null if it shouldn't
425         *   be displayed.
426         * @param spn The service provider name.  May be null if it shouldn't be displayed.
427         */
428        void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn);
429
430        /**
431         * Called when the ringer mode changes.
432         * @param state the current ringer state, as defined in
433         * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
434         */
435        void onRingerModeChanged(int state);
436
437        /**
438         * Called when the phone state changes. String will be one of:
439         * {@link TelephonyManager#EXTRA_STATE_IDLE}
440         * {@link TelephonyManager@EXTRA_STATE_RINGING}
441         * {@link TelephonyManager#EXTRA_STATE_OFFHOOK
442         */
443        void onPhoneStateChanged(String newState);
444    }
445
446    /**
447     * Callback to notify of sim state change.
448     */
449    interface SimStateCallback {
450        void onSimStateChanged(IccCard.State simState);
451    }
452
453    /**
454     * Register to receive notifications about general keyguard information
455     * (see {@link InfoCallback}.
456     * @param callback The callback.
457     */
458    public void registerInfoCallback(InfoCallback callback) {
459        if (!mInfoCallbacks.contains(callback)) {
460            mInfoCallbacks.add(callback);
461        } else {
462            Log.e(TAG, "Object tried to add another INFO callback", new Exception("Whoops"));
463        }
464    }
465
466    /**
467     * Register to be notified of sim state changes.
468     * @param callback The callback.
469     */
470    public void registerSimStateCallback(SimStateCallback callback) {
471        if (!mSimStateCallbacks.contains(callback)) {
472            mSimStateCallbacks.add(callback);
473        } else {
474            Log.e(TAG, "Object tried to add another SIM callback", new Exception("Whoops"));
475        }
476    }
477
478    public IccCard.State getSimState() {
479        return mSimState;
480    }
481
482    /**
483     * Report that the user succesfully entered the sim pin so we
484     * have the information earlier than waiting for the intent
485     * broadcast from the telephony code.
486     */
487    public void reportSimPinUnlocked() {
488        mSimState = IccCard.State.READY;
489    }
490
491    public boolean isKeyguardBypassEnabled() {
492        return mKeyguardBypassEnabled;
493    }
494
495    public boolean isDevicePluggedIn() {
496        return isPluggedIn(mBatteryStatus);
497    }
498
499    public boolean isDeviceCharged() {
500        return mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL
501                || mBatteryLevel >= 100; // in case a particular device doesn't flag it
502    }
503
504    public int getBatteryLevel() {
505        return mBatteryLevel;
506    }
507
508    public boolean shouldShowBatteryInfo() {
509        return isPluggedIn(mBatteryStatus) || isBatteryLow(mBatteryLevel);
510    }
511
512    public CharSequence getTelephonyPlmn() {
513        return mTelephonyPlmn;
514    }
515
516    public CharSequence getTelephonySpn() {
517        return mTelephonySpn;
518    }
519
520    /**
521     * @return Whether the device is provisioned (whether they have gone through
522     *   the setup wizard)
523     */
524    public boolean isDeviceProvisioned() {
525        return mDeviceProvisioned;
526    }
527
528    public int getFailedAttempts() {
529        return mFailedAttempts;
530    }
531
532    public void clearFailedAttempts() {
533        mFailedAttempts = 0;
534    }
535
536    public void reportFailedAttempt() {
537        mFailedAttempts++;
538    }
539}
540