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

分析:
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





<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" />

    
    <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加载的内容,我们修改下,在这个齿轮的左边加上电池信息和图片。


<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


<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>

你可能感兴趣的:(Android8.0,framework)