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