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 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;
128 this.simState = IccCard.State.UNKNOWN;
130 } else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
131 this.simState = IccCard.State.NETWORK_LOCKED;
133 this.simState = IccCard.State.UNKNOWN;
137 public String toString() {
138 return simState.toString();
142 public KeyguardUpdateMonitor(Context context) {
145 mHandler = new Handler() {
147 public void handleMessage(Message msg) {
149 case MSG_TIME_UPDATE:
152 case MSG_BATTERY_UPDATE:
153 handleBatteryUpdate(msg.arg1, msg.arg2);
155 case MSG_CARRIER_INFO_UPDATE:
156 handleCarrierInfoUpdate();
158 case MSG_SIM_STATE_CHANGE:
159 handleSimStateChange((SimArgs) msg.obj);
161 case MSG_RINGER_MODE_CHANGED:
162 handleRingerModeChange(msg.arg1);
164 case MSG_PHONE_STATE_CHANGED:
165 handlePhoneStateChanged((String)msg.obj);
171 mKeyguardBypassEnabled = context.getResources().getBoolean(
172 com.android.internal.R.bool.config_bypass_keyguard_if_slider_open);
174 mDeviceProvisioned = Settings.Secure.getInt(
175 mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
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) {
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;
191 if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
195 mContext.getContentResolver().registerContentObserver(
196 Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
197 false, mContentObserver);
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;
205 // take a guess to start
206 mSimState = IccCard.State.READY;
207 mBatteryStatus = BATTERY_STATUS_FULL;
210 mTelephonyPlmn = getDefaultPlmn();
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() {
224 public void onReceive(Context context, Intent intent) {
225 final String action = intent.getAction();
226 if (DEBUG) Log.d(TAG, "received broadcast " + action);
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(
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));
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);
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);
275 * Handle {@link #MSG_TIME_UPDATE}
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();
285 * Handle {@link #MSG_BATTERY_UPDATE}
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);
301 * Handle {@link #MSG_CARRIER_INFO_UPDATE}
303 private void handleCarrierInfoUpdate() {
304 if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
305 + ", spn = " + mTelephonySpn);
307 for (int i = 0; i < mInfoCallbacks.size(); i++) {
308 mInfoCallbacks.get(i).onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
313 * Handle {@link #MSG_SIM_STATE_CHANGE}
315 private void handleSimStateChange(SimArgs simArgs) {
316 final IccCard.State state = simArgs.simState;
319 Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
320 + "state resolved to " + state.toString());
323 if (state != IccCard.State.UNKNOWN && state != mSimState) {
325 for (int i = 0; i < mSimStateCallbacks.size(); i++) {
326 mSimStateCallbacks.get(i).onSimStateChanged(state);
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.
335 private boolean isPluggedIn(int status) {
336 return status == BATTERY_STATUS_CHARGING || status == BATTERY_STATUS_FULL;
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) {
349 // change in battery level while plugged in
350 if (isPluggedIn && mBatteryLevel != batteryLevel) {
355 // not plugged in and below threshold
356 if (isBatteryLow(batteryLevel) && batteryLevel != mBatteryLevel) {
363 private boolean isBatteryLow(int batteryLevel) {
364 return batteryLevel < LOW_BATTERY_THRESHOLD;
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.
371 private CharSequence getTelephonyPlmnFrom(Intent intent) {
372 if (intent.getBooleanExtra(EXTRA_SHOW_PLMN, false)) {
373 final String plmn = intent.getStringExtra(EXTRA_PLMN);
377 return getDefaultPlmn();
384 * @return The default plmn (no service)
386 private CharSequence getDefaultPlmn() {
387 return mContext.getResources().getText(
388 R.string.lockscreen_carrier_default);
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.
395 private CharSequence getTelephonySpnFrom(Intent intent) {
396 if (intent.getBooleanExtra(EXTRA_SHOW_SPN, false)) {
397 final String spn = intent.getStringExtra(EXTRA_SPN);
406 * Remove the given observer from being registered from any of the kinds
408 * @param observer The observer to remove (an instance of {@link ConfigurationChangeCallback},
409 * {@link InfoCallback} or {@link SimStateCallback}
411 public void removeCallback(Object observer) {
412 mInfoCallbacks.remove(observer);
413 mSimStateCallbacks.remove(observer);
417 * Callback for general information relevant to lock screen.
419 interface InfoCallback {
420 void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel);
421 void onTimeChanged();
424 * @param plmn The operator name of the registered network. May be null if it shouldn't
426 * @param spn The service provider name. May be null if it shouldn't be displayed.
428 void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn);
431 * Called when the ringer mode changes.
432 * @param state the current ringer state, as defined in
433 * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
435 void onRingerModeChanged(int state);
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
443 void onPhoneStateChanged(String newState);
447 * Callback to notify of sim state change.
449 interface SimStateCallback {
450 void onSimStateChanged(IccCard.State simState);
454 * Register to receive notifications about general keyguard information
455 * (see {@link InfoCallback}.
456 * @param callback The callback.
458 public void registerInfoCallback(InfoCallback callback) {
459 if (!mInfoCallbacks.contains(callback)) {
460 mInfoCallbacks.add(callback);
462 Log.e(TAG, "Object tried to add another INFO callback", new Exception("Whoops"));
467 * Register to be notified of sim state changes.
468 * @param callback The callback.
470 public void registerSimStateCallback(SimStateCallback callback) {
471 if (!mSimStateCallbacks.contains(callback)) {
472 mSimStateCallbacks.add(callback);
474 Log.e(TAG, "Object tried to add another SIM callback", new Exception("Whoops"));
478 public IccCard.State getSimState() {
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.
487 public void reportSimPinUnlocked() {
488 mSimState = IccCard.State.READY;
491 public boolean isKeyguardBypassEnabled() {
492 return mKeyguardBypassEnabled;
495 public boolean isDevicePluggedIn() {
496 return isPluggedIn(mBatteryStatus);
499 public boolean isDeviceCharged() {
500 return mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL
501 || mBatteryLevel >= 100; // in case a particular device doesn't flag it
504 public int getBatteryLevel() {
505 return mBatteryLevel;
508 public boolean shouldShowBatteryInfo() {
509 return isPluggedIn(mBatteryStatus) || isBatteryLow(mBatteryLevel);
512 public CharSequence getTelephonyPlmn() {
513 return mTelephonyPlmn;
516 public CharSequence getTelephonySpn() {
517 return mTelephonySpn;
521 * @return Whether the device is provisioned (whether they have gone through
524 public boolean isDeviceProvisioned() {
525 return mDeviceProvisioned;
528 public int getFailedAttempts() {
529 return mFailedAttempts;
532 public void clearFailedAttempts() {
536 public void reportFailedAttempt() {