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