OSDN Git Service

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