diff options
| author | 2016-04-06 16:15:14 -0700 | |
|---|---|---|
| committer | 2016-04-08 14:12:05 -0700 | |
| commit | da62fdcd502e772546bd98c016ab20fff6ee62e7 (patch) | |
| tree | 0d4fa8cd16f9c1ceb8cc9ec2bfded98d67e207d0 | |
| parent | 0e89f9804420a9b667eeaadff7916c227e6608cf (diff) | |
For Auto, display battery status as that of a connected device.
For Android Auto Embedded, we would like to display the battery status
of a device that is connected via Bluetooth and not the battery status
of the device itself (which would not make sense in Auto).
To accomplish this, introduce a new CarBatteryController that only
monitors the status of battery via Bluetooth. Note that AAE is not
explicitly handling the battery icon that appears in the quick settings
because this will be hidden for Auto.
This CarBatteryController implements a new BatteryController interface.
What used to be the BatteryController has been moved to the
BatteryControllerImpl class.
Bug: 28002775
Change-Id: I2285bcbd3d207cdcc1ac5a98ec3685d4fff0f0d9
5 files changed, 545 insertions, 157 deletions
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 000000000000..03b51c653ac4 --- /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<BatteryStateChangeCallback> 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 4add3cbfd2f9..811687c41a48 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 d036fe4807cd..16281710976d 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 bb3e1166f2aa..ea64fd8e96e3 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 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<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); - private final PowerManager mPowerManager; - private final Handler mHandler; - - 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 { +public interface BatteryController { + /** + * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}. + */ + void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + /** + * Sets if the current device is in power save mode. + */ + void setPowerSaveMode(boolean powerSave); + + /** + * Returns {@code true} if the device is currently in power save mode. + */ + boolean isPowerSave(); + + void addStateChangedCallback(BatteryStateChangeCallback cb); + void removeStateChangedCallback(BatteryStateChangeCallback cb); + + /** + * 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 000000000000..24207f3f35b9 --- /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<BatteryController.BatteryStateChangeCallback> 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); + } + } +} |