Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f1c4089 authored by Anthony Chen's avatar Anthony Chen Committed by android-build-merger
Browse files

Merge "For Auto, display battery status as that of a connected device." into nyc-dev

am: ab713b9c

* commit 'ab713b9c':
  For Auto, display battery status as that of a connected device.

Change-Id: Ibdff2a3355926c67fa9c145f92838b54a7084b50
parents c863e2d9 ab713b9c
Loading
Loading
Loading
Loading
+271 −0
Original line number Diff line number Diff line
/*
 * 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;
            }
        }
    };

}
+65 −5
Original line number Diff line number Diff line
@@ -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) {
+6 −3
Original line number Diff line number Diff line
@@ -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();
+24 −149
Original line number Diff line number Diff line
@@ -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());
    }
public interface BatteryController {
    /**
     * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
     */
    void dump(FileDescriptor fd, PrintWriter pw, String[] args);

    private void setPowerSave(boolean powerSave) {
        if (powerSave == mPowerSave) return;
        mPowerSave = powerSave;
        if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
        firePowerSaveChanged();
    }
    /**
     * Sets if the current device is in power save mode.
     */
    void setPowerSaveMode(boolean powerSave);

    private void fireBatteryLevelChanged() {
        final int N = mChangeCallbacks.size();
        for (int i = 0; i < N; i++) {
            mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
        }
    }
    /**
     * Returns {@code true} if the device is currently in power save mode.
     */
    boolean isPowerSave();

    private void firePowerSaveChanged() {
        final int N = mChangeCallbacks.size();
        for (int i = 0; i < N; i++) {
            mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
        }
    }
    void addStateChangedCallback(BatteryStateChangeCallback cb);
    void removeStateChangedCallback(BatteryStateChangeCallback cb);

    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);
    }
+179 −0

File added.

Preview size limit exceeded, changes collapsed.