Implement advanced device battery prediction
This CL implements prediction of advanced device to let users know what time their advanced device will be out of battery. Bug: 153706138 Test: make -j42 SettingsGoogle Change-Id: Iadf2f1fa425ff5f0fa1abed681d82d13c392db62
This commit is contained in:
@@ -64,4 +64,15 @@
|
|||||||
android:layout_marginStart="4dp"/>
|
android:layout_marginStart="4dp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bt_battery_prediction"
|
||||||
|
style="@style/TextAppearance.EntityHeaderSummary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@@ -467,4 +467,7 @@
|
|||||||
|
|
||||||
<!-- Whether to show the Preference for Adaptive connectivity -->
|
<!-- Whether to show the Preference for Adaptive connectivity -->
|
||||||
<bool name="config_show_adaptive_connectivity">false</bool>
|
<bool name="config_show_adaptive_connectivity">false</bool>
|
||||||
|
|
||||||
|
<!-- Authority of advanced device battery prediction -->
|
||||||
|
<string name="config_battery_prediction_authority" translatable="false"></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -18,8 +18,10 @@ package com.android.settings.bluetooth;
|
|||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
@@ -49,12 +51,14 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
|||||||
import com.android.settingslib.core.lifecycle.events.OnDestroy;
|
import com.android.settingslib.core.lifecycle.events.OnDestroy;
|
||||||
import com.android.settingslib.core.lifecycle.events.OnStart;
|
import com.android.settingslib.core.lifecycle.events.OnStart;
|
||||||
import com.android.settingslib.core.lifecycle.events.OnStop;
|
import com.android.settingslib.core.lifecycle.events.OnStop;
|
||||||
|
import com.android.settingslib.utils.StringUtil;
|
||||||
import com.android.settingslib.utils.ThreadUtils;
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
import com.android.settingslib.widget.LayoutPreference;
|
import com.android.settingslib.widget.LayoutPreference;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class adds a header with device name and status (connected/disconnected, etc.).
|
* This class adds a header with device name and status (connected/disconnected, etc.).
|
||||||
@@ -64,7 +68,22 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
private static final String TAG = "AdvancedBtHeaderCtrl";
|
private static final String TAG = "AdvancedBtHeaderCtrl";
|
||||||
private static final int LOW_BATTERY_LEVEL = 15;
|
private static final int LOW_BATTERY_LEVEL = 15;
|
||||||
private static final int CASE_LOW_BATTERY_LEVEL = 19;
|
private static final int CASE_LOW_BATTERY_LEVEL = 19;
|
||||||
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
|
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||||
|
|
||||||
|
private static final String PATH = "time_remaining";
|
||||||
|
private static final String QUERY_PARAMETER_ADDRESS = "address";
|
||||||
|
private static final String QUERY_PARAMETER_BATTERY_ID = "battery_id";
|
||||||
|
private static final String QUERY_PARAMETER_BATTERY_LEVEL = "battery_level";
|
||||||
|
private static final String QUERY_PARAMETER_TIMESTAMP = "timestamp";
|
||||||
|
private static final String BATTERY_ESTIMATE = "battery_estimate";
|
||||||
|
private static final String ESTIMATE_READY = "estimate_ready";
|
||||||
|
private static final String DATABASE_ID = "id";
|
||||||
|
private static final String DATABASE_BLUETOOTH = "Bluetooth";
|
||||||
|
private static final long TIME_OF_HOUR = TimeUnit.SECONDS.toMillis(3600);
|
||||||
|
private static final long TIME_OF_MINUTE = TimeUnit.SECONDS.toMillis(60);
|
||||||
|
private static final int LEFT_DEVICE_ID = 1;
|
||||||
|
private static final int RIGHT_DEVICE_ID = 2;
|
||||||
|
private static final int CASE_DEVICE_ID = 3;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
LayoutPreference mLayoutPreference;
|
LayoutPreference mLayoutPreference;
|
||||||
@@ -168,19 +187,22 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
|
BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON,
|
||||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
|
BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
|
||||||
BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
|
BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
|
||||||
R.string.bluetooth_left_name);
|
R.string.bluetooth_left_name,
|
||||||
|
LEFT_DEVICE_ID);
|
||||||
|
|
||||||
updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle),
|
updateSubLayout(mLayoutPreference.findViewById(R.id.layout_middle),
|
||||||
BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
|
BluetoothDevice.METADATA_UNTETHERED_CASE_ICON,
|
||||||
BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
|
BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
|
||||||
BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
|
BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
|
||||||
R.string.bluetooth_middle_name);
|
R.string.bluetooth_middle_name,
|
||||||
|
CASE_DEVICE_ID);
|
||||||
|
|
||||||
updateSubLayout(mLayoutPreference.findViewById(R.id.layout_right),
|
updateSubLayout(mLayoutPreference.findViewById(R.id.layout_right),
|
||||||
BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
|
BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON,
|
||||||
BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
|
BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
|
||||||
BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
|
BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
|
||||||
R.string.bluetooth_right_name);
|
R.string.bluetooth_right_name,
|
||||||
|
RIGHT_DEVICE_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +226,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateSubLayout(LinearLayout linearLayout, int iconMetaKey, int batteryMetaKey,
|
private void updateSubLayout(LinearLayout linearLayout, int iconMetaKey, int batteryMetaKey,
|
||||||
int chargeMetaKey, int titleResId) {
|
int chargeMetaKey, int titleResId, int batteryId) {
|
||||||
if (linearLayout == null) {
|
if (linearLayout == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -217,11 +239,15 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
|
|
||||||
final int batteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice, batteryMetaKey);
|
final int batteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice, batteryMetaKey);
|
||||||
final boolean charging = BluetoothUtils.getBooleanMetaData(bluetoothDevice, chargeMetaKey);
|
final boolean charging = BluetoothUtils.getBooleanMetaData(bluetoothDevice, chargeMetaKey);
|
||||||
if (DBG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "updateSubLayout() icon : " + iconMetaKey + ", battery : " + batteryMetaKey
|
Log.d(TAG, "updateSubLayout() icon : " + iconMetaKey + ", battery : " + batteryMetaKey
|
||||||
+ ", charge : " + chargeMetaKey + ", batteryLevel : " + batteryLevel
|
+ ", charge : " + chargeMetaKey + ", batteryLevel : " + batteryLevel
|
||||||
+ ", charging : " + charging + ", iconUri : " + iconUri);
|
+ ", charging : " + charging + ", iconUri : " + iconUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (batteryId != CASE_DEVICE_ID) {
|
||||||
|
showBatteryPredictionIfNecessary(linearLayout, batteryId, batteryLevel);
|
||||||
|
}
|
||||||
if (batteryLevel != BluetoothUtils.META_INT_ERROR) {
|
if (batteryLevel != BluetoothUtils.META_INT_ERROR) {
|
||||||
linearLayout.setVisibility(View.VISIBLE);
|
linearLayout.setVisibility(View.VISIBLE);
|
||||||
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
|
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
|
||||||
@@ -238,6 +264,64 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
textView.setVisibility(View.VISIBLE);
|
textView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showBatteryPredictionIfNecessary(LinearLayout linearLayout, int batteryId,
|
||||||
|
int batteryLevel) {
|
||||||
|
ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
|
final Uri contentUri = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(mContext.getString(R.string.config_battery_prediction_authority))
|
||||||
|
.appendPath(PATH)
|
||||||
|
.appendPath(DATABASE_ID)
|
||||||
|
.appendPath(DATABASE_BLUETOOTH)
|
||||||
|
.appendQueryParameter(QUERY_PARAMETER_ADDRESS, mCachedDevice.getAddress())
|
||||||
|
.appendQueryParameter(QUERY_PARAMETER_BATTERY_ID, String.valueOf(batteryId))
|
||||||
|
.appendQueryParameter(QUERY_PARAMETER_BATTERY_LEVEL,
|
||||||
|
String.valueOf(batteryLevel))
|
||||||
|
.appendQueryParameter(QUERY_PARAMETER_TIMESTAMP,
|
||||||
|
String.valueOf(System.currentTimeMillis()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final String[] columns = new String[] {BATTERY_ESTIMATE, ESTIMATE_READY};
|
||||||
|
final Cursor cursor =
|
||||||
|
mContext.getContentResolver().query(contentUri, columns, null, null, null);
|
||||||
|
if (cursor == null) {
|
||||||
|
Log.w(TAG, "showBatteryPredictionIfNecessary() cursor is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
|
||||||
|
final int estimateReady =
|
||||||
|
cursor.getInt(cursor.getColumnIndex(ESTIMATE_READY));
|
||||||
|
final long batteryEstimate =
|
||||||
|
cursor.getLong(cursor.getColumnIndex(BATTERY_ESTIMATE));
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "showBatteryTimeIfNecessary() batteryId : " + batteryId
|
||||||
|
+ ", ESTIMATE_READY : " + estimateReady
|
||||||
|
+ ", BATTERY_ESTIMATE : " + batteryEstimate);
|
||||||
|
}
|
||||||
|
showBatteryPredictionIfNecessary(estimateReady, batteryEstimate,
|
||||||
|
linearLayout);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void showBatteryPredictionIfNecessary(int estimateReady, long batteryEstimate,
|
||||||
|
LinearLayout linearLayout) {
|
||||||
|
ThreadUtils.postOnMainThread(() -> {
|
||||||
|
final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
|
||||||
|
if (estimateReady == 1) {
|
||||||
|
textView.setVisibility(View.VISIBLE);
|
||||||
|
textView.setText(StringUtil.formatElapsedTime(mContext, batteryEstimate, false));
|
||||||
|
} else {
|
||||||
|
textView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void showBatteryIcon(LinearLayout linearLayout, int level, boolean charging,
|
private void showBatteryIcon(LinearLayout linearLayout, int level, boolean charging,
|
||||||
int batteryMetaKey) {
|
int batteryMetaKey) {
|
||||||
final int lowBatteryLevel =
|
final int lowBatteryLevel =
|
||||||
@@ -279,7 +363,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
|
final BluetoothDevice bluetoothDevice = mCachedDevice.getDevice();
|
||||||
final String iconUri = BluetoothUtils.getStringMetaData(bluetoothDevice,
|
final String iconUri = BluetoothUtils.getStringMetaData(bluetoothDevice,
|
||||||
BluetoothDevice.METADATA_MAIN_ICON);
|
BluetoothDevice.METADATA_MAIN_ICON);
|
||||||
if (DBG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "updateDisconnectLayout() iconUri : " + iconUri);
|
Log.d(TAG, "updateDisconnectLayout() iconUri : " + iconUri);
|
||||||
}
|
}
|
||||||
if (iconUri != null) {
|
if (iconUri != null) {
|
||||||
|
@@ -42,6 +42,7 @@ import com.android.settings.fuelgauge.BatteryMeterView;
|
|||||||
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
|
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
|
||||||
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
|
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||||
|
import com.android.settingslib.utils.StringUtil;
|
||||||
import com.android.settingslib.widget.LayoutPreference;
|
import com.android.settingslib.widget.LayoutPreference;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -285,6 +286,68 @@ public class AdvancedBluetoothDetailsHeaderControllerTest {
|
|||||||
verify(mBitmap).recycle();
|
verify(mBitmap).recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void showBatteryPredictionIfNecessary_estimateReadyIsAvailable_showView() {
|
||||||
|
mController.showBatteryPredictionIfNecessary(1, 14218009,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_left));
|
||||||
|
mController.showBatteryPredictionIfNecessary(1, 14218009,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_middle));
|
||||||
|
mController.showBatteryPredictionIfNecessary(1, 14218009,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_right));
|
||||||
|
|
||||||
|
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
|
||||||
|
View.VISIBLE);
|
||||||
|
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_middle),
|
||||||
|
View.VISIBLE);
|
||||||
|
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
|
||||||
|
View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void showBatteryPredictionIfNecessary_estimateReadyIsNotAvailable_notShowView() {
|
||||||
|
mController.showBatteryPredictionIfNecessary(0, 14218009,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_left));
|
||||||
|
mController.showBatteryPredictionIfNecessary(0, 14218009,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_middle));
|
||||||
|
mController.showBatteryPredictionIfNecessary(0, 14218009,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_right));
|
||||||
|
|
||||||
|
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_left),
|
||||||
|
View.GONE);
|
||||||
|
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_middle),
|
||||||
|
View.GONE);
|
||||||
|
assertBatteryPredictionVisible(mLayoutPreference.findViewById(R.id.layout_right),
|
||||||
|
View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void showBatteryPredictionIfNecessary_estimateReadyIsAvailable_showCorrectValue() {
|
||||||
|
final String leftBatteryPrediction =
|
||||||
|
StringUtil.formatElapsedTime(mContext, 12000000, false).toString();
|
||||||
|
final String rightBatteryPrediction =
|
||||||
|
StringUtil.formatElapsedTime(mContext, 1200000, false).toString();
|
||||||
|
|
||||||
|
mController.showBatteryPredictionIfNecessary(1, 12000000,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_left));
|
||||||
|
mController.showBatteryPredictionIfNecessary(1, 1200000,
|
||||||
|
mLayoutPreference.findViewById(R.id.layout_right));
|
||||||
|
|
||||||
|
assertBatteryPrediction(mLayoutPreference.findViewById(R.id.layout_left),
|
||||||
|
leftBatteryPrediction);
|
||||||
|
assertBatteryPrediction(mLayoutPreference.findViewById(R.id.layout_right),
|
||||||
|
rightBatteryPrediction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertBatteryPredictionVisible(LinearLayout linearLayout, int visible) {
|
||||||
|
final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
|
||||||
|
assertThat(textView.getVisibility()).isEqualTo(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertBatteryPrediction(LinearLayout linearLayout, String prediction) {
|
||||||
|
final TextView textView = linearLayout.findViewById(R.id.bt_battery_prediction);
|
||||||
|
assertThat(textView.getText().toString()).isEqualTo(prediction);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
|
private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
|
||||||
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
|
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
|
||||||
assertThat(textView.getText().toString()).isEqualTo(
|
assertThat(textView.getText().toString()).isEqualTo(
|
||||||
|
Reference in New Issue
Block a user