Android8.0 在settings中添加蓝牙耳机的电池电量信息

来源:互联网 发布:时雨改二数据 编辑:程序博客网 时间:2024/05/16 09:17

分析:
1.获取蓝牙耳机电池电量
参考之前的文章 获取蓝牙耳机电池电量

2.分析Bluetooth settings布局
packages/apps/Settigns/src/com/android/settings/bluetooth/BluetoothSettings.java
进入蓝牙界面就是这里。这里怎么加载的还没弄清楚,主要修改的也不是这里。mPairedDevicesCategory就是配对的设备。

主要看BluetoothDevicePreference.java它就是你所看到的每一条蓝牙记录,就是list中的item。包括配对和发现的设备,都是这个。我们就是修改这个,在最右边的设置齿轮那里加上蓝牙耳机的电池电量。

BluetoothDevicePreference继承GearPreference,GearPreference继承RestrictedPreference,RestrictedPreference继承TwoTargetPreference,TwoTargetPreference继承Preference。
直接看TwoTargetPreference的布局preference_two_target.xml

<?xml version="1.0" encoding="utf-8"?><!--  Copyright (C) 2017 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.  --><!-- Based off preference_material_settings.xml except that ripple on only on the left side. --><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:minHeight="?android:attr/listPreferredItemHeight"    android:gravity="center_vertical"    android:background="@android:color/transparent"    android:clipToPadding="false">    <LinearLayout        android:layout_width="0dp"        android:layout_height="match_parent"        android:layout_weight="1"        android:background="?android:attr/selectableItemBackground"        android:gravity="start|center_vertical"        android:paddingStart="?android:attr/listPreferredItemPaddingStart">        <LinearLayout            android:id="@+id/icon_container"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:minWidth="56dp"            android:orientation="horizontal"            android:paddingEnd="12dp"            android:paddingTop="4dp"            android:paddingBottom="4dp">            <com.android.internal.widget.PreferenceImageView                android:id="@android:id/icon"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:maxWidth="48dp"                android:maxHeight="48dp" />        </LinearLayout>        <RelativeLayout            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_weight="1"            android:paddingTop="16dp"            android:paddingBottom="16dp">            <TextView                android:id="@android:id/title"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:singleLine="true"                android:textAppearance="?android:attr/textAppearanceListItem"                android:ellipsize="marquee" />            <TextView                android:id="@android:id/summary"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_below="@android:id/title"                android:layout_alignStart="@android:id/title"                android:textAppearance="?android:attr/textAppearanceListItemSecondary"                android:textColor="?android:attr/textColorSecondary"                android:maxLines="10" />        </RelativeLayout>    </LinearLayout>    <include layout="@layout/preference_two_target_divider" />    <!-- Preference should place its actual preference widget here. -->    <LinearLayout        android:id="@android:id/widget_frame"        android:layout_width="wrap_content"        android:layout_height="match_parent"        android:minWidth="64dp"        android:gravity="center"        android:orientation="vertical" /></LinearLayout>

只看widget_frame,这里是留给后面子类定制的。

TwoTargetPreference.java中的setWidgetLayoutResource就是去设置widget_frame,首先会判断子类是否有重写,没有就不去设置。我们这边的BluetoothDevicePreference是最后的子类,重写了getSecondTargetResId()方法,所以最终会把BluetoothDevicePreference设置的布局加载进去。我们只需要修改BluetoothDevicePreference中的getSecondTargetResId()。把我们要的布局放进去。

/* * Copyright (C) 2017 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.settingslib;import android.content.Context;import android.support.v7.preference.Preference;import android.support.v7.preference.PreferenceViewHolder;import android.util.AttributeSet;import android.view.View;public class TwoTargetPreference extends Preference {    public TwoTargetPreference(Context context, AttributeSet attrs,            int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        init();    }    public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    public TwoTargetPreference(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public TwoTargetPreference(Context context) {        super(context);        init();    }    private void init() {        setLayoutResource(R.layout.preference_two_target);        final int secondTargetResId = getSecondTargetResId();        if (secondTargetResId != 0) {            setWidgetLayoutResource(secondTargetResId);        }    }    @Override    public void onBindViewHolder(PreferenceViewHolder holder) {        super.onBindViewHolder(holder);        final View divider = holder.findViewById(R.id.two_target_divider);        final View widgetFrame = holder.findViewById(android.R.id.widget_frame);        final boolean shouldHideSecondTarget = shouldHideSecondTarget();        if (divider != null) {            divider.setVisibility(shouldHideSecondTarget ? View.GONE : View.VISIBLE);        }        if (widgetFrame != null) {            widgetFrame.setVisibility(shouldHideSecondTarget ? View.GONE : View.VISIBLE);        }    }    protected boolean shouldHideSecondTarget() {        return getSecondTargetResId() == 0;    }    protected int getSecondTargetResId() {        return 0;    }}

3.自定义布局
最后面的imageview就是原来BluetoothDevicePreference加载的内容,我们修改下,在这个齿轮的左边加上电池信息和图片。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="90dp"    android:layout_height="match_parent"    android:orientation="horizontal">    <LinearLayout    android:id="@+id/battery_layout"    android:layout_gravity="right"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_weight="1"        android:orientation="horizontal">        <TextView        android:id="@+id/battery_level"            android:layout_gravity="center"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:singleLine="true"            android:text="0%" />        <ImageView        android:id="@+id/battery_level_image"            android:scaleType="center"            android:layout_gravity="center"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />    </LinearLayout>        <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_weight="1"        android:orientation="horizontal">           <LinearLayout        android:layout_width="wrap_content"        android:layout_height="match_parent"        android:gravity="start|center_vertical"        android:orientation="horizontal"        android:paddingTop="16dp"        android:paddingBottom="16dp">        <View            android:layout_width="1dp"            android:layout_height="match_parent"            android:background="?android:attr/dividerVertical" />    </LinearLayout>    <ImageView        android:id="@+id/settings_button"        android:layout_width="wrap_content"        android:layout_height="match_parent"        android:layout_gravity="center"        android:background="?android:attr/selectableItemBackground"        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"        android:paddingStart="?android:attr/listPreferredItemPaddingEnd"        android:scaleType="center"        android:src="@drawable/ic_settings"        android:contentDescription="@string/settings_button" />        </LinearLayout></LinearLayout>

实现:
1.在framework层监听蓝牙电池信息
在BluetoothManagerService中添加蓝牙电池的监听,把读取到的值保存下来,这里保存电量和mac地址,方便后面使用。
为什么还要mac地址,因为这个广播1分钟发一次,所以要把电量先保存下来,等进去蓝牙界面的时候去读取,但是如果这次连接的蓝牙耳机不支持读电量,那么如果不用mac比较,就会把之前的蓝牙耳机电量读到了。

BluetoothManagerService(Context context) {        IntentFilter filter = new IntentFilter();        filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);        filter.addAction(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED);        filter.addAction(Intent.ACTION_SETTING_RESTORED);//add        filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);//add        filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY+"."+BluetoothAssignedNumbers.GOOGLE);        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);        mContext.registerReceiver(mReceiver, filter);}private final BroadcastReceiver mReceiver = new BroadcastReceiver() {    //省略原生代码                else if (BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT.equals(action)) {                   //aaron                String command = intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD);                if ("+IPHONEACCEV".equals(command)) {                    Object[] args = (Object[]) intent.getSerializableExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);                    if (args.length >= 3 && args[0] instanceof Integer && ((Integer)args[0])*2+1<=args.length) {                        for (int i=0;i<((Integer)args[0]);i++) {                            if (!(args[i*2+1] instanceof Integer) || !(args[i*2+2] instanceof Integer)) {                                continue;                            }                            if (args[i*2+1].equals(1)) {                                float level = (((Integer)args[i*2+2])+1)/10.0f;                                level=level*100;                                if (DBG) Slog.d(TAG, "battery   "+level);                                mLevel=(int) level;                                if (DBG) Slog.d(TAG, "battery mLevel  "+mLevel);                                break;                            }                        }                    }                    BluetoothDevice device=(BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE, null);                    if(device!=null) {                        String mac=device.getAddress();                        mMac=mac;                        if (DBG) Slog.d(TAG, "mac   "+mac);                    }                }            }        }}

这里添加一个新的方法,透个接口出去
这里用的本来就是AIDL,我们直接多添加一个方法就行。
frameworks/base/core/java/android/bluetooth/IBluetoothManager.aidl

/* * Copyright (C) 2012 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 android.bluetooth;import android.bluetooth.IBluetooth;import android.bluetooth.IBluetoothGatt;import android.bluetooth.IBluetoothManagerCallback;import android.bluetooth.IBluetoothProfileServiceConnection;import android.bluetooth.IBluetoothStateChangeCallback;/** * System private API for talking with the Bluetooth service. * * {@hide} */interface IBluetoothManager{    IBluetooth registerAdapter(in IBluetoothManagerCallback callback);    void unregisterAdapter(in IBluetoothManagerCallback callback);    void registerStateChangeCallback(in IBluetoothStateChangeCallback callback);    void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback);    boolean isEnabled();    boolean enable(String packageName);    boolean enableNoAutoConnect(String packageName);    boolean disable(String packageName, boolean persist);    int getState();    IBluetoothGatt getBluetoothGatt();    boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);    void unbindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);    String getAddress();    String getName();    int getBatteryLevel(String mac);    boolean isBleScanAlwaysAvailable();    int updateBleAppCount(IBinder b, boolean enable, String packageName);    boolean isBleAppPresent();}

在java中添加实现

    public int getBatteryLevel(String mac) {        if (DBG) Slog.d(TAG, "getBatteryLevel mac   "+mac);        if (DBG) Slog.d(TAG, "getBatteryLevel mMac   "+mMac);        if (DBG) Slog.d(TAG, "getBatteryLevel mLevel   "+mLevel);        if (mac.equals(mMac)) {            if (DBG) Slog.d(TAG, "getBatteryLevel return   "+mLevel);            return mLevel;        } else {            if (DBG) Slog.d(TAG, "getBatteryLevel return   -2");            return -2;        }    }

frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java
在BluetoothAdapter.java中添加一个获取电池的方法,给settings使用。

BluetoothAdapter mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.getBatteryLevel(mCachedDevice.getDevice().getAddress())

    /**     *      * @hide     */    public int getBatteryLevel(String mac) {        try {            int level = mManagerService.getBatteryLevel(mac);            return level;        } catch (RemoteException e) {            Log.e(TAG, "", e);        }        return -1;    }

2.Bluetooth Settings获取蓝牙电池信息
packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
每次加载布局的时候就去获取蓝牙电池

    @Override    public void onBindViewHolder(PreferenceViewHolder view) {        ......            setBattery(view);        super.onBindViewHolder(view);    }
    private void setBattery(PreferenceViewHolder view) {        LinearLayout batteryLayout= (LinearLayout)view.findViewById(R.id.battery_layout);        TextView batteryTextView=(TextView)view.findViewById(R.id.battery_level);        ImageView batteryImage=(ImageView) view.findViewById(R.id.battery_level_image);        batteryImage.setImageResource(R.drawable.bluetooth_device_battery);        batteryLayout.setVisibility(View.INVISIBLE);        if(mCachedDevice.isConnected()) {            int level=0;            BluetoothAdapter  mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();            level=mBluetoothAdapter.getBatteryLevel(mCachedDevice.getDevice().getAddress());            if(level>=0) {                batteryLayout.setVisibility(View.VISIBLE);                batteryTextView.setText(Integer.toString(level)+"%");                                batteryImage.setImageLevel(level);            }        }    }

在BluetoothSettings中同时也监听蓝牙耳机电池的广播,因为每次更新view,也就是进来的时候会抓。如果一直在这个界面不动,怎么办,那就要实时更新。
BluetoothSettings.java

    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            final String action = intent.getAction();            if (action.equals(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT)) {                String command = intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD);                if ("+IPHONEACCEV".equals(command)) {                    if (mLocalAdapter != null) {                        Log.i("b", "mIntentReceiver updatecontent ");                        updateContent(mLocalAdapter.getBluetoothState());                    }                }            }        }    };

3.布局
BluetoothDevicePreference.java

    @Override    protected int getSecondTargetResId() {        return R.layout.preference_widget_gear_ble_battery;    }

preference_widget_gear_ble_battery.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="90dp"    android:layout_height="match_parent"    android:orientation="horizontal">    <LinearLayout    android:id="@+id/battery_layout"    android:layout_gravity="right"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_weight="1"        android:orientation="horizontal">        <TextView        android:id="@+id/battery_level"            android:layout_gravity="center"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:singleLine="true"            android:text="0%" />        <ImageView        android:id="@+id/battery_level_image"            android:scaleType="center"            android:layout_gravity="center"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />    </LinearLayout>        <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_weight="1"        android:orientation="horizontal">           <LinearLayout        android:layout_width="wrap_content"        android:layout_height="match_parent"        android:gravity="start|center_vertical"        android:orientation="horizontal"        android:paddingTop="16dp"        android:paddingBottom="16dp">        <View            android:layout_width="1dp"            android:layout_height="match_parent"            android:background="?android:attr/dividerVertical" />    </LinearLayout>    <ImageView        android:id="@+id/settings_button"        android:layout_width="wrap_content"        android:layout_height="match_parent"        android:layout_gravity="center"        android:background="?android:attr/selectableItemBackground"        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"        android:paddingStart="?android:attr/listPreferredItemPaddingEnd"        android:scaleType="center"        android:src="@drawable/ic_settings"        android:contentDescription="@string/settings_button" />        </LinearLayout></LinearLayout>