2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.policy.impl;
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;
41 import com.android.internal.telephony.IccCard;
42 import com.android.internal.telephony.TelephonyIntents;
44 import android.telephony.TelephonyManager;
45 import android.util.Log;
46 import com.android.internal.R;
47 import com.google.android.collect.Lists;
49 import java.util.ArrayList;
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
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'...
61 public class KeyguardUpdateMonitor {
63 static private final String TAG = "KeyguardUpdateMonitor";
64 static private final boolean DEBUG = false;
66 /* package */ static final int LOW_BATTERY_THRESHOLD = 20;
68 private final Context mContext;
70 private IccCard.State mSimState = IccCard.State.READY;
72 private boolean mKeyguardBypassEnabled;
74 private boolean mDeviceProvisioned;
76 private int mBatteryLevel;
78 private int mBatteryStatus;
80 private CharSequence mTelephonyPlmn;
81 private CharSequence mTelephonySpn;
83 private int mFailedAttempts = 0;
85 private Handler mHandler;
87 private ArrayList<InfoCallback> mInfoCallbacks = Lists.newArrayList();
88 private ArrayList<SimStateCallback> mSimStateCallbacks = Lists.newArrayList();
89 private ContentObserver mContentObserver;
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;
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.
107 private static class SimArgs {
109 public final IccCard.State simState;
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");
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);
120 if (IccCard.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
122 this.simState = IccCard.State.PERM_DISABLED;
124 this.simState = IccCard.State.ABSENT;
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;
136 this.simState = IccCard.State.UNKNOWN;
138 } else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
139 this.simState = IccCard.State.NETWORK_LOCKED;
141 this.simState = IccCard.State.UNKNOWN;
145 public String toString() {
146 return simState.toString();
150 public KeyguardUpdateMonitor(Context context) {
153 mHandler = new Handler() {
155 public void handleMessage(Message msg) {
157 case MSG_TIME_UPDATE:
160 case MSG_BATTERY_UPDATE:
161 handleBatteryUpdate(msg.arg1, msg.arg2);
163 case MSG_CARRIER_INFO_UPDATE:
164 handleCarrierInfoUpdate();
166 case MSG_SIM_STATE_CHANGE:
167 handleSimStateChange((SimArgs) msg.obj);
169 case MSG_RINGER_MODE_CHANGED:
170 handleRingerModeChange(msg.arg1);
172 case MSG_PHONE_STATE_CHANGED:
173 handlePhoneStateChanged((String)msg.obj);
179 mKeyguardBypassEnabled = context.getResources().getBoolean(
180 com.android.internal.R.bool.config_bypass_keyguard_if_slider_open);
182 mDeviceProvisioned = Settings.Secure.getInt(
183 mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
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) {
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;
199 if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
203 mContext.getContentResolver().registerContentObserver(
204 Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
205 false, mContentObserver);
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;
213 // take a guess to start
214 mSimState = IccCard.State.READY;
215 mBatteryStatus = BATTERY_STATUS_FULL;
218 mTelephonyPlmn = getDefaultPlmn();
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() {
232 public void onReceive(Context context, Intent intent) {
233 final String action = intent.getAction();
234 if (DEBUG) Log.d(TAG, "received broadcast " + action);
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(
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));
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);
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);
283 * Handle {@link #MSG_TIME_UPDATE}
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();
293 * Handle {@link #MSG_BATTERY_UPDATE}
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);
309 * Handle {@link #MSG_CARRIER_INFO_UPDATE}
311 private void handleCarrierInfoUpdate() {
312 if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
313 + ", spn = " + mTelephonySpn);
315 for (int i = 0; i < mInfoCallbacks.size(); i++) {
316 mInfoCallbacks.get(i).onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
321 * Handle {@link #MSG_SIM_STATE_CHANGE}
323 private void handleSimStateChange(SimArgs simArgs) {
324 final IccCard.State state = simArgs.simState;
327 Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
328 + "state resolved to " + state.toString());
331 if (state != IccCard.State.UNKNOWN && state != mSimState) {
333 for (int i = 0; i < mSimStateCallbacks.size(); i++) {
334 mSimStateCallbacks.get(i).onSimStateChanged(state);
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.
343 private boolean isPluggedIn(int status) {
344 return status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL;
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) {
357 // change in battery level while plugged in
358 if (isPluggedIn && mBatteryLevel != batteryLevel) {
363 // not plugged in and below threshold
364 if (isBatteryLow(batteryLevel) && batteryLevel != mBatteryLevel) {
371 private boolean isBatteryLow(int batteryLevel) {
372 return batteryLevel < LOW_BATTERY_THRESHOLD;
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.
379 private CharSequence getTelephonyPlmnFrom(Intent intent) {
380 if (intent.getBooleanExtra(EXTRA_SHOW_PLMN, false)) {
381 final String plmn = intent.getStringExtra(EXTRA_PLMN);
385 return getDefaultPlmn();
392 * @return The default plmn (no service)
394 private CharSequence getDefaultPlmn() {
395 return mContext.getResources().getText(
396 R.string.lockscreen_carrier_default);
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.
403 private CharSequence getTelephonySpnFrom(Intent intent) {
404 if (intent.getBooleanExtra(EXTRA_SHOW_SPN, false)) {
405 final String spn = intent.getStringExtra(EXTRA_SPN);
414 * Remove the given observer from being registered from any of the kinds
416 * @param observer The observer to remove (an instance of {@link ConfigurationChangeCallback},
417 * {@link InfoCallback} or {@link SimStateCallback}
419 public void removeCallback(Object observer) {
420 mInfoCallbacks.remove(observer);
421 mSimStateCallbacks.remove(observer);
425 * Callback for general information relevant to lock screen.
427 interface InfoCallback {
428 void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);
429 void onTimeChanged();
432 * @param plmn The operator name of the registered network. May be null if it shouldn't
434 * @param spn The service provider name. May be null if it shouldn't be displayed.
436 void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn);
439 * Called when the ringer mode changes.
440 * @param state the current ringer state, as defined in
441 * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
443 void onRingerModeChanged(int state);
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
451 void onPhoneStateChanged(String newState);
455 * Callback to notify of sim state change.
457 interface SimStateCallback {
458 void onSimStateChanged(IccCard.State simState);
462 * Register to receive notifications about general keyguard information
463 * (see {@link InfoCallback}.
464 * @param callback The callback.
466 public void registerInfoCallback(InfoCallback callback) {
467 if (!mInfoCallbacks.contains(callback)) {
468 mInfoCallbacks.add(callback);
470 Log.e(TAG, "Object tried to add another INFO callback", new Exception("Whoops"));
475 * Register to be notified of sim state changes.
476 * @param callback The callback.
478 public void registerSimStateCallback(SimStateCallback callback) {
479 if (!mSimStateCallbacks.contains(callback)) {
480 mSimStateCallbacks.add(callback);
482 Log.e(TAG, "Object tried to add another SIM callback", new Exception("Whoops"));
486 public IccCard.State getSimState() {
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.
495 public void reportSimUnlocked() {
496 mSimState = IccCard.State.READY;
499 public boolean isKeyguardBypassEnabled() {
500 return mKeyguardBypassEnabled;
503 public boolean isDevicePluggedIn() {
504 return isPluggedIn(mBatteryStatus);
507 public boolean isDeviceCharged() {
508 return mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL
509 || mBatteryLevel >= 100; // in case a particular device doesn't flag it
512 public int getBatteryLevel() {
513 return mBatteryLevel;
516 public boolean shouldShowBatteryInfo() {
517 return isPluggedIn(mBatteryStatus) || isBatteryLow(mBatteryLevel);
520 public CharSequence getTelephonyPlmn() {
521 return mTelephonyPlmn;
524 public CharSequence getTelephonySpn() {
525 return mTelephonySpn;
529 * @return Whether the device is provisioned (whether they have gone through
532 public boolean isDeviceProvisioned() {
533 return mDeviceProvisioned;
536 public int getFailedAttempts() {
537 return mFailedAttempts;
540 public void clearFailedAttempts() {
544 public void reportFailedAttempt() {