diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java new file mode 100644 index 0000000000000..03b51c653ac45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadsetClient; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothProfile.ServiceListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; + +import com.android.systemui.statusbar.policy.BatteryController; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * A {@link BatteryController} that is specific to the Auto use-case. For Auto, the battery icon + * displays the battery status of a device that is connected via bluetooth and not the system's + * battery. + */ +public class CarBatteryController extends BroadcastReceiver implements BatteryController { + private static final String TAG = "CarBatteryController"; + + // According to the Bluetooth HFP 1.5 specification, battery levels are indicated by a + // value from 1-5, where these values represent the following: + // 0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5 + // As a result, set the level as the average within that range. + private static final int BATTERY_LEVEL_EMPTY = 0; + private static final int BATTERY_LEVEL_1 = 12; + private static final int BATTERY_LEVEL_2 = 28; + private static final int BATTERY_LEVEL_3 = 63; + private static final int BATTERY_LEVEL_4 = 87; + private static final int BATTERY_LEVEL_FULL = 100; + + private static final int INVALID_BATTERY_LEVEL = -1; + + private final Context mContext; + + private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + private BluetoothHeadsetClient mBluetoothHeadsetClient; + + private final ArrayList mChangeCallbacks = new ArrayList<>(); + + private int mLevel; + + /** + * An interface indicating the container of a View that will display what the information + * in the {@link CarBatteryController}. + */ + public interface BatteryViewHandler { + void hideBatteryView(); + void showBatteryView(); + } + + private BatteryViewHandler mBatteryViewHandler; + + public CarBatteryController(Context context) { + mContext = context; + + mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener, + BluetoothProfile.HEADSET_CLIENT); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CarBatteryController state:"); + pw.print(" mLevel="); + pw.println(mLevel); + } + + @Override + public void setPowerSaveMode(boolean powerSave) { + // No-op. No power save mode for the car. + } + + @Override + public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) { + mChangeCallbacks.add(cb); + + // There is no way to know if the phone is plugged in or charging via bluetooth, so pass + // false for these values. + cb.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */); + cb.onPowerSaveChanged(false /* isPowerSave */); + } + + @Override + public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) { + mChangeCallbacks.remove(cb); + } + + public void addBatteryViewHandler(BatteryViewHandler batteryViewHandler) { + mBatteryViewHandler = batteryViewHandler; + } + + public void startListening() { + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT); + mContext.registerReceiver(this, filter); + } + + public void stopListening() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onReceive(). action: " + action); + } + + if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Received ACTION_AG_EVENT"); + } + + int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, + INVALID_BATTERY_LEVEL); + + updateBatteryLevel(batteryLevel); + + if (batteryLevel != INVALID_BATTERY_LEVEL && mBatteryViewHandler != null) { + mBatteryViewHandler.showBatteryView(); + } + } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); + Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: " + + oldState + " -> " + newState); + + } + BluetoothDevice device = + (BluetoothDevice)intent.getExtra(BluetoothDevice.EXTRA_DEVICE); + updateBatteryIcon(device, newState); + } + } + + /** + * Converts the battery level to a percentage that can be displayed on-screen and notifies + * any {@link BatteryStateChangeCallback}s of this. + */ + private void updateBatteryLevel(int batteryLevel) { + if (batteryLevel == INVALID_BATTERY_LEVEL) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Battery level invalid. Ignoring."); + } + return; + } + + // The battery level is a value between 0-5. Let the default battery level be 0. + switch (batteryLevel) { + case 5: + mLevel = BATTERY_LEVEL_FULL; + break; + case 4: + mLevel = BATTERY_LEVEL_4; + break; + case 3: + mLevel = BATTERY_LEVEL_3; + break; + case 2: + mLevel = BATTERY_LEVEL_2; + break; + case 1: + mLevel = BATTERY_LEVEL_1; + break; + case 0: + default: + mLevel = BATTERY_LEVEL_EMPTY; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Battery level: " + batteryLevel + "; setting mLevel as: " + mLevel); + } + + notifyBatteryLevelChanged(); + } + + /** + * Updates the display of the battery icon depending on the given connection state from the + * given {@link BluetoothDevice}. + */ + private void updateBatteryIcon(BluetoothDevice device, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Device connected"); + } + + if (mBatteryViewHandler != null) { + mBatteryViewHandler.showBatteryView(); + } + + if (mBluetoothHeadsetClient == null || device == null) { + return; + } + + // Check if battery information is available and immediately update. + Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device); + if (featuresBundle == null) { + return; + } + + int batteryLevel = featuresBundle.getInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, + INVALID_BATTERY_LEVEL); + updateBatteryLevel(batteryLevel); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Device disconnected"); + } + + if (mBatteryViewHandler != null) { + mBatteryViewHandler.hideBatteryView(); + } + } + } + + @Override + public boolean isPowerSave() { + // Power save is not valid for the car, so always return false. + return false; + } + + private void notifyBatteryLevelChanged() { + for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) { + mChangeCallbacks.get(i) + .onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */); + } + } + + private final ServiceListener mHfpServiceListener = new ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.HEADSET_CLIENT) { + mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy; + } + } + + @Override + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.HEADSET_CLIENT) { + mBluetoothHeadsetClient = null; + } + } + }; + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 4add3cbfd2f92..811687c41a48e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -22,37 +22,75 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.PixelFormat; -import android.os.Handler; -import android.os.Looper; -import android.os.RemoteException; +import android.util.Log; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.ViewStub; import android.view.WindowManager; - +import com.android.systemui.BatteryMeterView; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.phone.PhoneStatusBarView; +import com.android.systemui.statusbar.policy.BatteryController; /** * A status bar (and navigation bar) tailored for the automotive use case. */ -public class CarStatusBar extends PhoneStatusBar { +public class CarStatusBar extends PhoneStatusBar implements + CarBatteryController.BatteryViewHandler { + private static final String TAG = "CarStatusBar"; + private TaskStackListenerImpl mTaskStackListener; private CarNavigationBarView mCarNavigationBar; private CarNavigationBarController mController; private FullscreenUserSwitcher mFullscreenUserSwitcher; + private CarBatteryController mCarBatteryController; + private BatteryMeterView mBatteryMeterView; + @Override public void start() { super.start(); mTaskStackListener = new TaskStackListenerImpl(); SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener); registerPackageChangeReceivers(); + + mCarBatteryController.startListening(); + } + + @Override + public void destroy() { + mCarBatteryController.stopListening(); + super.destroy(); + } + + @Override + protected PhoneStatusBarView makeStatusBarView() { + PhoneStatusBarView statusBarView = super.makeStatusBarView(); + + mBatteryMeterView = ((BatteryMeterView) statusBarView.findViewById(R.id.battery)); + + // By default, the BatteryMeterView should not be visible. It will be toggled visible + // when a device has connected by bluetooth. + mBatteryMeterView.setVisibility(View.GONE); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView); + } + + return statusBarView; + } + + @Override + protected BatteryController createBatteryController() { + mCarBatteryController = new CarBatteryController(mContext); + mCarBatteryController.addBatteryViewHandler(this); + return mCarBatteryController; } @Override @@ -85,6 +123,28 @@ public class CarStatusBar extends PhoneStatusBar { } + @Override + public void showBatteryView() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView); + } + + if (mBatteryMeterView != null) { + mBatteryMeterView.setVisibility(View.VISIBLE); + } + } + + @Override + public void hideBatteryView() { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView); + } + + if (mBatteryMeterView != null) { + mBatteryMeterView.setVisibility(View.GONE); + } + } + private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 933d5bdb743dc..ec1ad4664b8a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -21,9 +21,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.app.ActivityManager; -import android.app.ActivityManager.StackId; import android.app.ActivityManagerNative; -import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.Notification; import android.app.PendingIntent; @@ -147,6 +145,7 @@ import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChan import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; +import com.android.systemui.statusbar.policy.BatteryControllerImpl; import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.CastControllerImpl; @@ -826,7 +825,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Other icons mLocationController = new LocationControllerImpl(mContext, mHandlerThread.getLooper()); // will post a notification - mBatteryController = new BatteryController(mContext); + mBatteryController = createBatteryController(); mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() { @Override public void onPowerSaveChanged(boolean isPowerSave) { @@ -943,6 +942,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mStatusBarView; } + protected BatteryController createBatteryController() { + return new BatteryControllerImpl(mContext); + } + @Override protected void reInflateViews() { super.reInflateViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index bb3e1166f2aa6..ea64fd8e96e3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -16,158 +16,33 @@ package com.android.systemui.statusbar.policy; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.Handler; -import android.os.PowerManager; -import android.util.Log; - import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -public class BatteryController extends BroadcastReceiver { - private static final String TAG = "BatteryController"; +public interface BatteryController { + /** + * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}. + */ + void dump(FileDescriptor fd, PrintWriter pw, String[] args); - public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST"; + /** + * Sets if the current device is in power save mode. + */ + void setPowerSaveMode(boolean powerSave); - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + /** + * Returns {@code true} if the device is currently in power save mode. + */ + boolean isPowerSave(); - private final ArrayList mChangeCallbacks = new ArrayList<>(); - private final PowerManager mPowerManager; - private final Handler mHandler; + void addStateChangedCallback(BatteryStateChangeCallback cb); + void removeStateChangedCallback(BatteryStateChangeCallback cb); - private int mLevel; - private boolean mPluggedIn; - private boolean mCharging; - private boolean mCharged; - private boolean mPowerSave; - private boolean mTestmode = false; - - public BatteryController(Context context) { - mHandler = new Handler(); - mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); - filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING); - filter.addAction(ACTION_LEVEL_TEST); - context.registerReceiver(this, filter); - - updatePowerSave(); - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("BatteryController state:"); - pw.print(" mLevel="); pw.println(mLevel); - pw.print(" mPluggedIn="); pw.println(mPluggedIn); - pw.print(" mCharging="); pw.println(mCharging); - pw.print(" mCharged="); pw.println(mCharged); - pw.print(" mPowerSave="); pw.println(mPowerSave); - } - - public void setPowerSaveMode(boolean powerSave) { - mPowerManager.setPowerSaveMode(powerSave); - } - - public void addStateChangedCallback(BatteryStateChangeCallback cb) { - mChangeCallbacks.add(cb); - cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); - cb.onPowerSaveChanged(mPowerSave); - } - - public void removeStateChangedCallback(BatteryStateChangeCallback cb) { - mChangeCallbacks.remove(cb); - } - - public void onReceive(final Context context, Intent intent) { - final String action = intent.getAction(); - if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - if (mTestmode && !intent.getBooleanExtra("testmode", false)) return; - mLevel = (int)(100f - * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) - / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)); - mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; - - final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, - BatteryManager.BATTERY_STATUS_UNKNOWN); - mCharged = status == BatteryManager.BATTERY_STATUS_FULL; - mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING; - - fireBatteryLevelChanged(); - } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) { - updatePowerSave(); - } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) { - setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false)); - } else if (action.equals(ACTION_LEVEL_TEST)) { - mTestmode = true; - mHandler.post(new Runnable() { - int curLevel = 0; - int incr = 1; - int saveLevel = mLevel; - boolean savePlugged = mPluggedIn; - Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED); - @Override - public void run() { - if (curLevel < 0) { - mTestmode = false; - dummy.putExtra("level", saveLevel); - dummy.putExtra("plugged", savePlugged); - dummy.putExtra("testmode", false); - } else { - dummy.putExtra("level", curLevel); - dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC - : 0); - dummy.putExtra("testmode", true); - } - context.sendBroadcast(dummy); - - if (!mTestmode) return; - - curLevel += incr; - if (curLevel == 100) { - incr *= -1; - } - mHandler.postDelayed(this, 200); - } - }); - } - } - - public boolean isPowerSave() { - return mPowerSave; - } - - private void updatePowerSave() { - setPowerSave(mPowerManager.isPowerSaveMode()); - } - - private void setPowerSave(boolean powerSave) { - if (powerSave == mPowerSave) return; - mPowerSave = powerSave; - if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off")); - firePowerSaveChanged(); - } - - private void fireBatteryLevelChanged() { - final int N = mChangeCallbacks.size(); - for (int i = 0; i < N; i++) { - mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); - } - } - - private void firePowerSaveChanged() { - final int N = mChangeCallbacks.size(); - for (int i = 0; i < N; i++) { - mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave); - } - } - - public interface BatteryStateChangeCallback { + /** + * A listener that will be notified whenever a change in battery level or power save mode + * has occurred. + */ + interface BatteryStateChangeCallback { void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging); void onPowerSaveChanged(boolean isPowerSave); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java new file mode 100644 index 0000000000000..24207f3f35b9e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.PowerManager; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Default implementation of a {@link BatteryController}. This controller monitors for battery + * level change events that are broadcasted by the system. + */ +public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController { + private static final String TAG = "BatteryController"; + + public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final ArrayList mChangeCallbacks = new ArrayList<>(); + private final PowerManager mPowerManager; + private final Handler mHandler; + + protected int mLevel; + protected boolean mPluggedIn; + protected boolean mCharging; + protected boolean mCharged; + protected boolean mPowerSave; + private boolean mTestmode = false; + + public BatteryControllerImpl(Context context) { + mHandler = new Handler(); + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING); + filter.addAction(ACTION_LEVEL_TEST); + context.registerReceiver(this, filter); + + updatePowerSave(); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("BatteryController state:"); + pw.print(" mLevel="); pw.println(mLevel); + pw.print(" mPluggedIn="); pw.println(mPluggedIn); + pw.print(" mCharging="); pw.println(mCharging); + pw.print(" mCharged="); pw.println(mCharged); + pw.print(" mPowerSave="); pw.println(mPowerSave); + } + + @Override + public void setPowerSaveMode(boolean powerSave) { + mPowerManager.setPowerSaveMode(powerSave); + } + + @Override + public void addStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) { + mChangeCallbacks.add(cb); + cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); + cb.onPowerSaveChanged(mPowerSave); + } + + @Override + public void removeStateChangedCallback(BatteryController.BatteryStateChangeCallback cb) { + mChangeCallbacks.remove(cb); + } + + @Override + public void onReceive(final Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { + if (mTestmode && !intent.getBooleanExtra("testmode", false)) return; + mLevel = (int)(100f + * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) + / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)); + mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; + + final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_UNKNOWN); + mCharged = status == BatteryManager.BATTERY_STATUS_FULL; + mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING; + + fireBatteryLevelChanged(); + } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) { + updatePowerSave(); + } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) { + setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false)); + } else if (action.equals(ACTION_LEVEL_TEST)) { + mTestmode = true; + mHandler.post(new Runnable() { + int curLevel = 0; + int incr = 1; + int saveLevel = mLevel; + boolean savePlugged = mPluggedIn; + Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED); + @Override + public void run() { + if (curLevel < 0) { + mTestmode = false; + dummy.putExtra("level", saveLevel); + dummy.putExtra("plugged", savePlugged); + dummy.putExtra("testmode", false); + } else { + dummy.putExtra("level", curLevel); + dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC + : 0); + dummy.putExtra("testmode", true); + } + context.sendBroadcast(dummy); + + if (!mTestmode) return; + + curLevel += incr; + if (curLevel == 100) { + incr *= -1; + } + mHandler.postDelayed(this, 200); + } + }); + } + } + + @Override + public boolean isPowerSave() { + return mPowerSave; + } + + private void updatePowerSave() { + setPowerSave(mPowerManager.isPowerSaveMode()); + } + + private void setPowerSave(boolean powerSave) { + if (powerSave == mPowerSave) return; + mPowerSave = powerSave; + if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off")); + firePowerSaveChanged(); + } + + protected void fireBatteryLevelChanged() { + final int N = mChangeCallbacks.size(); + for (int i = 0; i < N; i++) { + mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); + } + } + + private void firePowerSaveChanged() { + final int N = mChangeCallbacks.size(); + for (int i = 0; i < N; i++) { + mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave); + } + } +}