Snap for 7314842 from e14cc634dc to sc-release

Change-Id: I9459ee8072eead32b91f477fbbe9d9a461ee0a07
This commit is contained in:
android-build-team Robot
2021-04-27 01:08:24 +00:00
31 changed files with 1099 additions and 147 deletions

View File

@@ -26,7 +26,7 @@
android:id="@+id/chart_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="45dp"
android:layout_marginBottom="30dp"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary"
android:text="@string/battery_usage_chart_graph_hint" />
@@ -34,9 +34,16 @@
<com.android.settings.fuelgauge.BatteryChartView
android:id="@+id/battery_chart"
android:layout_width="match_parent"
android:layout_height="141dp"
android:layout_height="150dp"
android:layout_marginBottom="16dp"
android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/companion_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 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.
-->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
android:contentDescription="@string/remove_association_button"
android:id="@+id/remove_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_clear"
android:background="@android:color/transparent"/>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="16dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
</LinearLayout>

View File

@@ -205,6 +205,9 @@
<dimen name="bluetooth_pairing_edittext_padding">21dp</dimen>
<dimen name="bluetooth_checkbox_padding">16dp</dimen>
<!-- CompanionAppWidgetPreferencce Padding -->
<dimen name="bluetooth_companion_app_widget">20dp</dimen>
<!-- WiFi Preferences -->
<dimen name="wifi_divider_height">1px</dimen>
<dimen name="wifi_ap_band_checkbox_padding">16dp</dimen>
@@ -441,6 +444,7 @@
<dimen name="subtitle_bottom_padding">24dp</dimen>
<!-- Battery usage chart view component -->
<dimen name="chartview_text_padding">3dp</dimen>
<dimen name="chartview_divider_width">1dp</dimen>
<dimen name="chartview_divider_height">4dp</dimen>
<dimen name="chartview_trapezoid_radius">3dp</dimen>

View File

@@ -1815,6 +1815,10 @@
<string name="bluetooth_device_mac_address">Device\'s Bluetooth address: <xliff:g id="address">%1$s</xliff:g></string>
<!-- Bluetooth device details. The title of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=60] -->
<string name="bluetooth_unpair_dialog_title">Forget device?</string>
<!-- Content Description for companion device app associations removal button [CHAR LIMIT=28]-->
<string name="remove_association_button">Remove association</string>
<!-- Bluetooth device details companion apps. The title of a confirmation dialog for remove an app. [CHAR LIMIT=60] -->
<string name="bluetooth_companion_app_remove_association_dialog_title">Disconnect App?</string>
<!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
<string name="bluetooth_unpair_dialog_body" product="default">Your phone will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
@@ -1822,12 +1826,16 @@
<string name="bluetooth_unpair_dialog_body" product="tablet">Your tablet will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
<!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
<string name="bluetooth_unpair_dialog_body" product="device">Your device will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
<!-- Bluetooth device details companion apps. The body of confirmation dialog for remove association. [CHAR LIMIT=60] -->
<string name="bluetooth_companion_app_body"><xliff:g id="app_name" example="App Name">%1$s</xliff:g> app will no longer connect to your <xliff:g id="device_name" example="Device Name">%2$s</xliff:g></string>
<!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=NONE] -->
<string name="bluetooth_untethered_unpair_dialog_body"><xliff:g id="device_name" example="Jack's headphone">%1$s</xliff:g> will no longer be paired with any device linked to this account</string>
<!-- Bluetooth device details. In the confirmation dialog for unpairing a paired device, this is the label on the button that will complete the unpairing action. -->
<string name="bluetooth_unpair_dialog_forget_confirm_button">Forget device</string>
<!-- Bluetooth device details companion apps. In the confirmation dialog for removing an associated app, this is the label on the button that will complete the disassociate action. [CHAR LIMIT=80] -->
<string name = "bluetooth_companion_app_remove_association_confirm_button">Disconnect app</string>
<!-- Bluetooth settings. The title of the screen to pick which profiles to connect to on the device. For example, headphones may have both A2DP and headset, this allows the users to choose which one they want to connect to. -->
<string name="bluetooth_connect_specific_profiles_title">Connect to\u2026</string>

View File

@@ -56,7 +56,6 @@
<com.android.settings.widget.SeekBarPreference
android:key="accessibility_button_opacity"
android:title="@string/accessibility_button_opacity_title"
android:selectable="true"
android:persistent="false"
settings:controller="com.android.settings.accessibility.FloatingMenuOpacityPreferenceController"/>

View File

@@ -44,6 +44,9 @@
settings:allowDividerBelow="true"
settings:allowDividerAbove="true"/>
<PreferenceCategory
android:key="device_companion_apps"/>
<PreferenceCategory
android:key="bluetooth_profiles"/>

View File

@@ -38,26 +38,31 @@
android:summary="@string/summary_placeholder"
settings:userRestriction="no_config_date_time"/>
<com.android.settingslib.RestrictedSwitchPreference
android:key="auto_zone"
android:title="@string/zone_auto_title"
settings:allowDividerAbove="true"
settings:userRestriction="no_config_date_time"/>
<PreferenceCategory
android:key="timezone_preference_category"
android:title="@string/date_time_set_timezone_title">
<com.android.settingslib.RestrictedPreference
android:key="timezone"
android:title="@string/date_time_set_timezone_title"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.datetime.timezone.TimeZoneSettings"
settings:userRestriction="no_config_date_time"
settings:keywords="@string/keywords_time_zone"/>
<com.android.settingslib.RestrictedSwitchPreference
android:key="auto_zone"
android:title="@string/zone_auto_title"
settings:allowDividerAbove="true"
settings:userRestriction="no_config_date_time"/>
<!-- This preference gets removed if location-based time zone detection is not supported -->
<SwitchPreference
android:key="location_time_zone_detection"
android:title="@string/location_time_zone_detection_toggle_title"
settings:controller="com.android.settings.datetime.LocationTimeZoneDetectionPreferenceController"
/>
<com.android.settingslib.RestrictedPreference
android:key="timezone"
android:title="@string/date_time_set_timezone_title"
android:summary="@string/summary_placeholder"
android:fragment="com.android.settings.datetime.timezone.TimeZoneSettings"
settings:userRestriction="no_config_date_time"
settings:keywords="@string/keywords_time_zone"/>
<!-- This preference gets removed if location-based time zone detection is not supported -->
<SwitchPreference
android:key="location_time_zone_detection"
android:title="@string/location_time_zone_detection_toggle_title"
settings:controller="com.android.settings.datetime.LocationTimeZoneDetectionPreferenceController"
/>
</PreferenceCategory>
<PreferenceCategory
android:key="time_format_preference_category"

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<com.android.settingslib.widget.LayoutPreference
android:key="header_view"
android:layout="@layout/settings_entity_header"
android:selectable="false"
android:order="-10000"
settings:allowDividerBelow="true"/>
<com.android.settingslib.widget.ActionButtonsPreference
android:key="action_buttons"
android:order="-9999"/>
<PreferenceCategory
android:title="@string/battery_detail_manage_title"
settings:allowDividerAbove="true">
<com.android.settingslib.RestrictedPreference
android:key="background_activity"
android:title="@string/background_activity_title"
android:selectable="true"
settings:userRestriction="no_control_apps"/>
<Preference
android:key="battery_optimization"
android:title="@string/high_power_apps"
android:summary="@string/high_power_off"
android:selectable="true"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/battery_detail_info_title">
<Preference
android:key="app_usage_foreground"
android:title="@string/battery_detail_foreground"
android:selectable="false"/>
<Preference
android:key="app_usage_background"
android:title="@string/battery_detail_background"
android:selectable="false"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -78,7 +78,7 @@
<!-- On lock screen notifications -->
<com.android.settings.RestrictedListPreference
android:key="privacy_lock_screen_notifications"
android:title="@string/lock_screen_notifications_title"
android:title="@string/lock_screen_notifs_title"
android:summary="@string/summary_placeholder"
settings:searchable="false"/>

View File

@@ -48,6 +48,7 @@ import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
@@ -94,6 +95,7 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.TabWidget;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.core.graphics.drawable.IconCompat;
@@ -111,6 +113,7 @@ import com.android.settings.dashboard.profileselector.ProfileFragmentBridge;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settingslib.widget.ActionBarShadowController;
import com.android.settingslib.widget.AdaptiveIcon;
import java.util.Iterator;
import java.util.List;
@@ -967,15 +970,36 @@ public final class Utils extends com.android.settingslib.Utils {
}
/**
* Sets the preference icon with a drawable that is scaled down to to avoid crashing Settings if
* it's too big.
* Gets the adaptive icon with a drawable that wrapped with an adaptive background using {@code
* backgroundColor} if it is not a {@link AdaptiveIconDrawable}
*
* If the given {@code icon} is too big, it will be auto scaled down to to avoid crashing
* Settings.
*/
public static void setSafeIcon(Preference pref, Drawable icon) {
public static Drawable getAdaptiveIcon(Context context, Drawable icon,
@ColorInt int backgroundColor) {
Drawable adaptiveIcon = getSafeIcon(icon);
if (!(adaptiveIcon instanceof AdaptiveIconDrawable)) {
adaptiveIcon = new AdaptiveIcon(context, adaptiveIcon);
((AdaptiveIcon) adaptiveIcon).setBackgroundColor(backgroundColor);
}
return adaptiveIcon;
}
/**
* Gets the icon with a drawable that is scaled down to to avoid crashing Settings if it's too
* big and not a {@link VectorDrawable}.
*/
public static Drawable getSafeIcon(Drawable icon) {
Drawable safeIcon = icon;
if ((icon != null) && !(icon instanceof VectorDrawable)) {
safeIcon = getSafeDrawable(icon, 500, 500);
}
pref.setIcon(safeIcon);
return safeIcon;
}
/**
@@ -985,7 +1009,7 @@ public final class Utils extends com.android.settingslib.Utils {
* @param maxWidth maximum width, in pixels.
* @param maxHeight maximum height, in pixels.
*/
public static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) {
private static Drawable getSafeDrawable(Drawable original, int maxWidth, int maxHeight) {
final int actualWidth = original.getMinimumWidth();
final int actualHeight = original.getMinimumHeight();

View File

@@ -28,6 +28,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -663,7 +664,7 @@ public class AccessibilitySettings extends DashboardFragment {
preference.setKey(key);
preference.setTitle(title);
preference.setSummary(summary);
Utils.setSafeIcon(preference, icon);
preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE));
preference.setFragment(fragment);
preference.setIconSize(ICON_SIZE_MEDIUM);
preference.setPersistent(false); // Disable SharedPreferences.

View File

@@ -124,7 +124,7 @@ public class PasswordsPreferenceController extends BasePreferenceController
serviceInfo,
serviceInfo.applicationInfo,
user);
Utils.setSafeIcon(pref, icon);
pref.setIcon(Utils.getSafeIcon(icon));
pref.setIntent(
new Intent(Intent.ACTION_MAIN)
.setClassName(serviceInfo.packageName, service.getPasswordsActivity()));

View File

@@ -66,7 +66,7 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC
}
if (!TextUtils.isEmpty(defaultAppLabel)) {
preference.setSummary(defaultAppLabel);
Utils.setSafeIcon(preference, getDefaultAppIcon());
preference.setIcon(Utils.getSafeIcon(getDefaultAppIcon()));
} else {
Log.d(TAG, "No default app");
preference.setSummary(R.string.app_list_preference_none);

View File

@@ -57,15 +57,15 @@ public class FaceSettingsAttentionPreferenceController extends FaceSettingsPrefe
private final GetFeatureCallback mGetFeatureCallback = new GetFeatureCallback() {
@Override
public void onCompleted(boolean success, int feature, boolean value) {
if (feature == FaceManager.FEATURE_REQUIRE_ATTENTION && success) {
if (!mFaceManager.hasEnrolledTemplates(getUserId())) {
mPreference.setEnabled(false);
} else {
mPreference.setEnabled(true);
mPreference.setChecked(value);
public void onCompleted(boolean success, int[] features, boolean[] featureState) {
boolean requireAttentionEnabled = false;
for (int i = 0; i < features.length; i++) {
if (features[i] == FaceManager.FEATURE_REQUIRE_ATTENTION) {
requireAttentionEnabled = featureState[i];
}
}
mPreference.setEnabled(success);
mPreference.setChecked(requireAttentionEnabled);
}
};

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2021 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.settings.bluetooth;
import static com.android.internal.util.CollectionUtils.filter;
import android.companion.Association;
import android.companion.CompanionDeviceManager;
import android.companion.ICompanionDeviceManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.google.common.base.Objects;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This class adds Companion Device app rows to launch the app or remove the associations
*/
public class BluetoothDetailsCompanionAppsController extends BluetoothDetailsController {
public static final String KEY_DEVICE_COMPANION_APPS = "device_companion_apps";
private static final String LOG_TAG = "BTCompanionController";
private CachedBluetoothDevice mCachedDevice;
@VisibleForTesting
PreferenceCategory mProfilesContainer;
@VisibleForTesting
CompanionDeviceManager mCompanionDeviceManager;
@VisibleForTesting
PackageManager mPackageManager;
public BluetoothDetailsCompanionAppsController(Context context,
PreferenceFragmentCompat fragment, CachedBluetoothDevice device, Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
mCachedDevice = device;
mCompanionDeviceManager = context.getSystemService(CompanionDeviceManager.class);
mPackageManager = context.getPackageManager();
lifecycle.addObserver(this);
}
@Override
protected void init(PreferenceScreen screen) {
mProfilesContainer = screen.findPreference(getPreferenceKey());
mProfilesContainer.setLayoutResource(R.layout.preference_companion_app);
}
private List<Association> getAssociations(String address) {
return filter(
mCompanionDeviceManager.getAllAssociations(),
a -> Objects.equal(address, a.getDeviceMacAddress()));
}
private static void removePreference(PreferenceCategory container, String packageName) {
Preference preference = container.findPreference(packageName);
if (preference != null) {
container.removePreference(preference);
}
}
private void removeAssociationDialog(String packageName, String address,
PreferenceCategory container, CharSequence appName, Context context) {
DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_POSITIVE) {
removeAssociation(packageName, address, container);
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setPositiveButton(
R.string.bluetooth_companion_app_remove_association_confirm_button,
dialogClickListener)
.setNegativeButton(android.R.string.cancel, dialogClickListener)
.setTitle(R.string.bluetooth_companion_app_remove_association_dialog_title)
.setMessage(mContext.getString(
R.string.bluetooth_companion_app_body, appName, mCachedDevice.getName()))
.show();
}
private static void removeAssociation(String packageName, String address,
PreferenceCategory container) {
try {
java.util.Objects.requireNonNull(ICompanionDeviceManager.Stub.asInterface(
ServiceManager.getService(
Context.COMPANION_DEVICE_SERVICE))).disassociate(
address, packageName);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
removePreference(container, packageName);
}
private CharSequence getAppName(String packageName) {
CharSequence appName = null;
try {
appName = mPackageManager.getApplicationLabel(
mPackageManager.getApplicationInfo(packageName, 0));
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Package Not Found", e);
}
return appName;
}
private List<String> getPreferencesNeedToShow(String address, PreferenceCategory container) {
List<String> preferencesToRemove = new ArrayList<>();
Set<String> packages = getAssociations(address)
.stream().map(Association::getPackageName)
.collect(Collectors.toSet());
for (int i = 0; i < container.getPreferenceCount(); i++) {
String preferenceKey = container.getPreference(i).getKey();
if (packages.isEmpty() || !packages.contains(preferenceKey)) {
preferencesToRemove.add(preferenceKey);
}
}
for (String preferenceName : preferencesToRemove) {
removePreference(container, preferenceName);
}
return packages.stream()
.filter(p -> container.findPreference(p) == null)
.collect(Collectors.toList());
}
/**
* Refreshes the state of the preferences for all the associations, possibly adding or
* removing preferences as needed.
*/
@Override
protected void refresh() {
updatePreferences(mContext, mCachedDevice.getAddress(), mProfilesContainer);
}
/**
* Add preferences for each association for the bluetooth device
*/
public void updatePreferences(Context context,
String address, PreferenceCategory container) {
Set<String> addedPackages = new HashSet<>();
for (String packageName : getPreferencesNeedToShow(address, container)) {
CharSequence appName = getAppName(packageName);
if (TextUtils.isEmpty(appName) || !addedPackages.add(packageName)) {
continue;
}
Drawable removeIcon = context.getResources().getDrawable(R.drawable.ic_clear);
CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference(
removeIcon,
v -> removeAssociationDialog(packageName, address, container, appName, context),
context
);
Drawable appIcon;
try {
appIcon = mPackageManager.getApplicationIcon(packageName);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Icon Not Found", e);
continue;
}
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
preference.setIcon(appIcon);
preference.setTitle(appName.toString());
preference.setOnPreferenceClickListener(v -> {
context.startActivity(intent);
return true;
});
preference.setKey(packageName);
preference.setVisible(true);
container.addPreference(preference);
}
}
@Override
public String getPreferenceKey() {
return KEY_DEVICE_COMPANION_APPS;
}
}

View File

@@ -185,6 +185,8 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment
lifecycle, mManager));
controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice,
lifecycle));
controllers.add(new BluetoothDetailsCompanionAppsController(context, this,
mCachedDevice, lifecycle));
controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
mCachedDevice, lifecycle));
controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 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.settings.bluetooth;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageButton;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/**
* A custom preference for companion device apps. Added a button for association removal
*/
public class CompanionAppWidgetPreference extends Preference {
private Drawable mWidgetIcon;
private View.OnClickListener mWidgetListener;
private int mImageButtonPadding;
public CompanionAppWidgetPreference(Drawable widgetIcon, View.OnClickListener widgetListener,
Context context) {
super(context);
mWidgetIcon = widgetIcon;
mWidgetListener = widgetListener;
mImageButtonPadding = context.getResources().getDimensionPixelSize(
R.dimen.bluetooth_companion_app_widget);
setWidgetLayoutResource(R.layout.companion_apps_remove_button_widget);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
ImageButton imageButton = (ImageButton) holder.findViewById(R.id.remove_button);
imageButton.setPadding(
mImageButtonPadding, mImageButtonPadding, mImageButtonPadding, mImageButtonPadding);
imageButton.setColorFilter(getContext().getColor(android.R.color.darker_gray));
imageButton.setImageDrawable(mWidgetIcon);
imageButton.setOnClickListener(mWidgetListener);
}
}

View File

@@ -88,6 +88,6 @@ public class CurrentDreamPreferenceController extends BasePreferenceController {
}
final GearPreference gearPref = (GearPreference) preference;
gearPref.setIconSize(RestrictedPreference.ICON_SIZE_SMALL);
Utils.setSafeIcon(gearPref, mBackend.getActiveIcon());
gearPref.setIcon(Utils.getSafeIcon(mBackend.getActiveIcon()));
}
}

View File

@@ -42,6 +42,8 @@ import com.android.settings.applications.appinfo.ButtonActionDialogFragment;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
@@ -61,7 +63,7 @@ import java.util.List;
*/
public class AdvancedPowerUsageDetail extends DashboardFragment implements
ButtonActionDialogFragment.AppButtonsDialogListener,
RadioButtonPreference.OnClickListener {
BatteryTipPreferenceController.BatteryTipListener, RadioButtonPreference.OnClickListener {
public static final String TAG = "AdvancedPowerDetail";
public static final String EXTRA_UID = "extra_uid";
@@ -73,6 +75,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent";
public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount";
private static final String KEY_PREF_FOREGROUND = "app_usage_foreground";
private static final String KEY_PREF_BACKGROUND = "app_usage_background";
private static final String KEY_PREF_HEADER = "header_view";
private static final String KEY_PREF_UNRESTRICTED = "unrestricted_pref";
private static final String KEY_PREF_OPTIMIZED = "optimized_pref";
@@ -92,7 +96,10 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
BatteryUtils mBatteryUtils;
@VisibleForTesting
BatteryOptimizeUtils mBatteryOptimizeUtils;
@VisibleForTesting
Preference mForegroundPreference;
@VisibleForTesting
Preference mBackgroundPreference;
@VisibleForTesting
Preference mFooterPreference;
@VisibleForTesting
@@ -101,18 +108,18 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
RadioButtonPreference mOptimizePreference;
@VisibleForTesting
RadioButtonPreference mUnrestrictedPreference;
private AppButtonsPreferenceController mAppButtonsPreferenceController;
private UnrestrictedPreferenceController mUnrestrictedPreferenceController;
private OptimizedPreferenceController mOptimizedPreferenceController;
private RestrictedPreferenceController mRestrictedPreferenceController;
@VisibleForTesting
boolean enableTriState = true;
private String mPackageName;
private AppButtonsPreferenceController mAppButtonsPreferenceController;
private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController;
// A wrapper class to carry LaunchBatteryDetailPage required arguments.
private static final class LaunchBatteryDetailPageArgs {
private String mUsagePercent;
private String mPackageName;
private String mAppLabel;
private String mSlotInformation;
private int mUid;
private int mIconId;
private int mConsumedPower;
@@ -124,18 +131,22 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
/** Launches battery details page for an individual battery consumer. */
public static void startBatteryDetailPage(
Activity caller, InstrumentedPreferenceFragment fragment,
BatteryDiffEntry diffEntry, String usagePercent) {
BatteryDiffEntry diffEntry, String usagePercent,
boolean isValidToShowSummary, String slotInformation) {
final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
// configure the launch argument.
launchArgs.mUsagePercent = usagePercent;
launchArgs.mPackageName = diffEntry.getPackageName();
launchArgs.mAppLabel = diffEntry.getAppLabel();
launchArgs.mSlotInformation = slotInformation;
launchArgs.mUid = (int) histEntry.mUid;
launchArgs.mIconId = diffEntry.getAppIconId();
launchArgs.mConsumedPower = (int) diffEntry.mConsumePower;
launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs;
launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs;
launchArgs.mForegroundTimeMs =
isValidToShowSummary ? diffEntry.mForegroundUsageTimeInMs : 0;
launchArgs.mBackgroundTimeMs =
isValidToShowSummary ? diffEntry.mBackgroundUsageTimeInMs : 0;
launchArgs.mIsUserEntry = histEntry.isUserEntry();
startBatteryDetailPage(caller, fragment, launchArgs);
}
@@ -227,22 +238,17 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME);
mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE);
mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER);
final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
if (enableTriState) {
onCreateForTriState(packageName);
} else {
mForegroundPreference = findPreference(KEY_PREF_FOREGROUND);
mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND);
}
mHeaderPreference = findPreference(KEY_PREF_HEADER);
mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED);
mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED);
mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED);
mUnrestrictedPreference.setOnClickListener(this);
mOptimizePreference.setOnClickListener(this);
mRestrictedPreference.setOnClickListener(this);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(
getContext(), getArguments().getInt(EXTRA_UID), mPackageName);
if (mPackageName != null) {
mAppEntry = mState.getEntry(mPackageName, UserHandle.myUserId());
if (packageName != null) {
mAppEntry = mState.getEntry(packageName, UserHandle.myUserId());
}
}
@@ -251,7 +257,11 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
super.onResume();
initHeader();
initPreference();
if (enableTriState) {
initPreferenceForTriState(getContext());
} else {
initPreference(getContext());
}
}
@VisibleForTesting
@@ -281,17 +291,39 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
}
final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
//TODO(b/178197718) Update layout to support multiple lines
controller.setSummary(getAppActiveTime(foregroundTimeMs, backgroundTimeMs));
if (enableTriState) {
final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
//TODO(b/178197718) Update layout to support multiple lines
controller.setSummary(getAppActiveTime(foregroundTimeMs, backgroundTimeMs));
}
controller.done(context, true /* rebindActions */);
}
@VisibleForTesting
void initPreference() {
final Context context = getContext();
void initPreference(Context context) {
final Bundle bundle = getArguments();
final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
mForegroundPreference.setSummary(
TextUtils.expandTemplate(getText(R.string.battery_used_for),
StringUtil.formatElapsedTime(
context,
foregroundTimeMs,
/* withSeconds */ false,
/* collapseTimeUnit */ false)));
mBackgroundPreference.setSummary(
TextUtils.expandTemplate(getText(R.string.battery_active_for),
StringUtil.formatElapsedTime(
context,
backgroundTimeMs,
/* withSeconds */ false,
/* collapseTimeUnit */ false)));
}
@VisibleForTesting
void initPreferenceForTriState(Context context) {
final String stateString;
final String footerString;
//TODO(b/178197718) Update strings
@@ -308,7 +340,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
} else {
//Present default string to normal app.
footerString = context.getString(R.string.manager_battery_usage_footer);
}
mFooterPreference.setTitle(Html.fromHtml(footerString, Html.FROM_HTML_MODE_COMPACT));
}
@@ -325,7 +356,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
@Override
protected int getPreferenceScreenResId() {
return R.xml.power_usage_detail;
return enableTriState ? R.xml.power_usage_detail : R.xml.power_usage_detail_legacy;
}
@Override
@@ -336,18 +367,20 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);
mAppButtonsPreferenceController = new AppButtonsPreferenceController(
(SettingsActivity) getActivity(), this, getSettingsLifecycle(), packageName, mState,
REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
(SettingsActivity) getActivity(), this, getSettingsLifecycle(), packageName,
mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
controllers.add(mAppButtonsPreferenceController);
mUnrestrictedPreferenceController =
new UnrestrictedPreferenceController(context, uid, packageName);
mOptimizedPreferenceController =
new OptimizedPreferenceController(context, uid, packageName);
mRestrictedPreferenceController =
new RestrictedPreferenceController(context, uid, packageName);
controllers.add(mUnrestrictedPreferenceController);
controllers.add(mOptimizedPreferenceController);
controllers.add(mRestrictedPreferenceController);
if (enableTriState) {
controllers.add(new UnrestrictedPreferenceController(context, uid, packageName));
controllers.add(new OptimizedPreferenceController(context, uid, packageName));
controllers.add(new RestrictedPreferenceController(context, uid, packageName));
} else {
mBackgroundActivityPreferenceController = new BackgroundActivityPreferenceController(
context, this, uid, packageName);
controllers.add(mBackgroundActivityPreferenceController);
controllers.add(new BatteryOptimizationPreferenceController(
(SettingsActivity) getActivity(), this, packageName));
}
return controllers;
}
@@ -367,6 +400,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
}
}
@Override
public void onBatteryTipHandled(BatteryTip batteryTip) {
mBackgroundActivityPreferenceController.updateSummary(
findPreference(mBackgroundActivityPreferenceController.getPreferenceKey()));
}
@Override
public void onRadioButtonClicked(RadioButtonPreference selected) {
updatePreferenceState(mUnrestrictedPreference, selected.getKey());
@@ -378,6 +417,19 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements
preference.setChecked(selectedKey.equals(preference.getKey()));
}
private void onCreateForTriState(String packageName) {
mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED);
mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED);
mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED);
mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE);
mUnrestrictedPreference.setOnClickListener(this);
mOptimizePreference.setOnClickListener(this);
mRestrictedPreference.setOnClickListener(this);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(
getContext(), getArguments().getInt(EXTRA_UID), packageName);
}
//TODO(b/178197718) Update method to support time period
private CharSequence getAppActiveTime(long foregroundTimeMs, long backgroundTimeMs) {
final long totalTimeMs = foregroundTimeMs + backgroundTimeMs;

View File

@@ -151,21 +151,22 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
final PowerGaugePreference powerPref = (PowerGaugePreference) preference;
final BatteryDiffEntry diffEntry = powerPref.getBatteryDiffEntry();
final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
final String packageName = histEntry.mPackageName;
// Checks whether the package is installed or not.
boolean isValidPackage = true;
if (histEntry.isAppEntry()) {
if (mBatteryUtils == null) {
mBatteryUtils = BatteryUtils.getInstance(mPrefContext);
}
isValidPackage = mBatteryUtils.getPackageUid(histEntry.mPackageName)
isValidPackage = mBatteryUtils.getPackageUid(packageName)
!= BatteryUtils.UID_NULL;
}
Log.d(TAG, String.format("handleClick() label=%s key=%s isValid:%b %s",
diffEntry.getAppLabel(), histEntry.getKey(), isValidPackage,
histEntry.mPackageName));
diffEntry.getAppLabel(), histEntry.getKey(), isValidPackage, packageName));
if (isValidPackage) {
AdvancedPowerUsageDetail.startBatteryDetailPage(
mActivity, mFragment, diffEntry, powerPref.getPercent());
mActivity, mFragment, diffEntry, powerPref.getPercent(),
isValidToShowSummary(packageName), getSlotInformation());
return true;
}
return false;
@@ -267,8 +268,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
|| (mTrapezoidIndex == trapezoidIndex && !isForce)) {
return false;
}
Log.d(TAG, String.format("refreshUi: index=%d batteryIndexedMap.size=%d",
mTrapezoidIndex, mBatteryIndexedMap.size()));
Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b",
trapezoidIndex, mBatteryIndexedMap.size(), isForce));
mTrapezoidIndex = trapezoidIndex;
mHandler.post(() -> {
@@ -471,11 +472,9 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs;
final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
// Checks whether the package is allowed to show summary or not.
for (CharSequence notAllowPackageName : mNotAllowShowSummaryPackages) {
if (TextUtils.equals(entry.getPackageName(), notAllowPackageName)) {
preference.setSummary(null);
return;
}
if (!isValidToShowSummary(entry.getPackageName())) {
preference.setSummary(null);
return;
}
String usageTimeSummary = null;
// Not shows summary for some system components without usage time.
@@ -515,6 +514,17 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
return mPrefContext.getString(resourceId, timeSequence);
}
private boolean isValidToShowSummary(String packageName) {
if (mNotAllowShowSummaryPackages != null) {
for (CharSequence notAllowPackageName : mNotAllowShowSummaryPackages) {
if (TextUtils.equals(packageName, notAllowPackageName)) {
return false;
}
}
}
return true;
}
private static String utcToLocalTime(long[] timestamps) {
final StringBuilder builder = new StringBuilder();
for (int index = 0; index < timestamps.length; index++) {

View File

@@ -22,11 +22,13 @@ import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.widget.AppCompatImageView;
@@ -38,6 +40,8 @@ import java.util.Locale;
/** A widget component to draw chart graph. */
public class BatteryChartView extends AppCompatImageView implements View.OnClickListener {
private static final String TAG = "BatteryChartView";
// For drawing the percentage information.
private static final String[] PERCENTAGES = new String[] {"100%", "50%", "0%"};
private static final int DEFAULT_TRAPEZOID_COUNT = 12;
/** Selects all trapezoid shapes. */
public static final int SELECTED_INDEX_ALL = -1;
@@ -58,8 +62,14 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
private int mTrapezoidColor;
private int mTrapezoidSolidColor;
private final int mDividerColor = Color.parseColor("#CDCCC5");
// For drawing the percentage information.
private int mTextPadding;
private final Rect mIndent = new Rect();
private final Rect[] mPercentageBound =
new Rect[] {new Rect(), new Rect(), new Rect()};
private int[] mLevels;
private Paint mTextPaint;
private Paint mDividerPaint;
private Paint mTrapezoidPaint;
private TrapezoidSlot[] mTrapezoidSlot;
@@ -128,6 +138,37 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mOnSelectListener = listener;
}
/** Sets the companion {@link TextView} for percentage information. */
public void setCompanionTextView(TextView textView) {
requestLayout();
if (textView != null) {
// Pre-draws the view first to load style atttributions into paint.
textView.draw(new Canvas());
mTextPaint = textView.getPaint();
} else {
mTextPaint = null;
}
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Measures text bounds and updates indent configuration.
if (mTextPaint != null) {
for (int index = 0; index < PERCENTAGES.length; index++) {
mTextPaint.getTextBounds(
PERCENTAGES[index], 0, PERCENTAGES[index].length(),
mPercentageBound[index]);
}
// Updates the indent configurations.
mIndent.top = mPercentageBound[0].height();
mIndent.right = mPercentageBound[0].width() + mTextPadding * 2;
Log.d(TAG, "setIndent:" + mPercentageBound[0]);
} else {
mIndent.set(0, 0, 0, 0);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -194,32 +235,56 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
mTrapezoidPaint.setPathEffect(
new CornerPathEffect(
resources.getDimensionPixelSize(R.dimen.chartview_trapezoid_radius)));
// Initializes for drawing text information.
mTextPadding = resources.getDimensionPixelSize(R.dimen.chartview_text_padding);
}
private void drawHorizontalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right;
final int height = getHeight() - mIndent.top - mIndent.bottom;
// Draws the top divider line for 100% curve.
float offsetY = mDividerWidth * 0.5f;
canvas.drawLine(0, offsetY, getWidth(), offsetY, mDividerPaint);
float offsetY = mIndent.top + mDividerWidth * .5f;
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
if (mTextPaint != null) {
canvas.drawText(
PERCENTAGES[0],
getWidth() - mPercentageBound[0].width(),
offsetY + mPercentageBound[0].height() *.5f , mTextPaint);
}
// Draws the center divider line for 50% curve.
final float availableSpace =
getHeight() - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight;
offsetY = mDividerWidth + availableSpace * 0.5f;
canvas.drawLine(0, offsetY, getWidth(), offsetY, mDividerPaint);
// Draws the center divider line for 0% curve.
offsetY = getHeight() - mDividerHeight - mDividerWidth * 0.5f;
canvas.drawLine(0, offsetY, getWidth(), offsetY, mDividerPaint);
height - mDividerWidth * 2 - mTrapezoidVOffset - mDividerHeight;
offsetY = mIndent.top + mDividerWidth + availableSpace * .5f;
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
if (mTextPaint != null) {
canvas.drawText(
PERCENTAGES[1],
getWidth() - mPercentageBound[1].width(),
offsetY + mPercentageBound[1].height() *.5f , mTextPaint);
}
// Draws the bottom divider line for 0% curve.
offsetY = mIndent.top + (height - mDividerHeight - mDividerWidth * .5f);
canvas.drawLine(0, offsetY, width, offsetY, mDividerPaint);
if (mTextPaint != null) {
canvas.drawText(
PERCENTAGES[2],
getWidth() - mPercentageBound[2].width(),
offsetY + mPercentageBound[2].height() *.5f , mTextPaint);
}
}
private void drawVerticalDividers(Canvas canvas) {
final int width = getWidth() - mIndent.right;
final int dividerCount = mTrapezoidCount + 1;
final float dividerSpace = dividerCount * mDividerWidth;
final float unitWidth = (getWidth() - dividerSpace) / (float) mTrapezoidCount;
final float startY = getHeight() - mDividerHeight;
final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * 0.5f;
final float unitWidth = (width - dividerSpace) / (float) mTrapezoidCount;
final float bottomY = getHeight() - mIndent.bottom;
final float startY = bottomY - mDividerHeight;
final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f;
// Draws each vertical dividers.
float startX = mDividerWidth * 0.5f;
float startX = mDividerWidth * .5f;
for (int index = 0; index < dividerCount; index++) {
canvas.drawLine(startX, startY, startX, getHeight(), mDividerPaint);
canvas.drawLine(startX, startY, startX, bottomY, mDividerPaint);
final float nextX = startX + mDividerWidth + unitWidth;
// Updates the trapezoid slots for drawing.
if (index < mTrapezoidSlot.length) {
@@ -236,8 +301,9 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
return;
}
final float trapezoidBottom =
getHeight() - mDividerHeight - mDividerWidth - mTrapezoidVOffset;
final float availableSpace = trapezoidBottom - mDividerWidth;
getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth
- mTrapezoidVOffset;
final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top;
final float unitHeight = availableSpace / 100f;
// Draws all trapezoid shapes into the canvas.
final Path trapezoidPath = new Path();
@@ -249,8 +315,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
// Configures the trapezoid paint color.
mTrapezoidPaint.setColor(
mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL
? mTrapezoidSolidColor
: mTrapezoidColor);
? mTrapezoidSolidColor
: mTrapezoidColor);
final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight);
final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight);
trapezoidPath.reset();

View File

@@ -100,6 +100,8 @@ public class BatteryHistoryPreference extends Preference {
}
if (mIsChartGraphEnabled) {
mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart);
mBatteryChartView.setCompanionTextView(
(TextView) view.findViewById(R.id.companion_text));
if (mChartPreferenceController != null) {
mChartPreferenceController.setBatteryChartView(mBatteryChartView);
}

View File

@@ -38,6 +38,7 @@ public class ExpandDividerPreference extends Preference {
private OnExpandListener mOnExpandListener;
private boolean mIsExpanded = false;
private String mTitleContent = null;
/** A callback listener for expand state is changed by users. */
public interface OnExpandListener {
@@ -72,6 +73,7 @@ public class ExpandDividerPreference extends Preference {
}
void setTitle(final String titleContent) {
mTitleContent = titleContent;
if (mTextView != null) {
mTextView.postDelayed(
() -> mTextView.setText(titleContent), 50);
@@ -95,5 +97,6 @@ public class ExpandDividerPreference extends Preference {
if (mImageView != null) {
mImageView.setImageResource(iconId);
}
setTitle(mTitleContent);
}
}

View File

@@ -37,7 +37,7 @@ class LocaleRecyclerView extends RecyclerView {
@Override
public boolean onTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) {
LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this.getAdapter();
if (adapter != null) {
adapter.doTheUpdate();

View File

@@ -205,7 +205,7 @@ public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFr
public RadioButtonPreference bindPreference(RadioButtonPreference pref,
String key, CandidateInfo info, String defaultKey) {
pref.setTitle(info.loadLabel());
Utils.setSafeIcon(pref, info.loadIcon());
pref.setIcon(Utils.getSafeIcon(info.loadIcon()));
pref.setKey(key);
if (TextUtils.equals(defaultKey, key)) {
pref.setChecked(true);

View File

@@ -1339,12 +1339,13 @@ public class WifiConfigController2 implements TextWatcher,
mIpAddressView = (TextView) mView.findViewById(R.id.ipaddress);
mIpAddressView.addTextChangedListener(this);
mGatewayView = (TextView) mView.findViewById(R.id.gateway);
mGatewayView.addTextChangedListener(this);
mGatewayView.addTextChangedListener(getIpConfigFieldsTextWatcher(mGatewayView));
mNetworkPrefixLengthView = (TextView) mView.findViewById(
R.id.network_prefix_length);
mNetworkPrefixLengthView.addTextChangedListener(this);
mNetworkPrefixLengthView.addTextChangedListener(
getIpConfigFieldsTextWatcher(mNetworkPrefixLengthView));
mDns1View = (TextView) mView.findViewById(R.id.dns1);
mDns1View.addTextChangedListener(this);
mDns1View.addTextChangedListener(getIpConfigFieldsTextWatcher(mDns1View));
mDns2View = (TextView) mView.findViewById(R.id.dns2);
mDns2View.addTextChangedListener(this);
}
@@ -1562,6 +1563,47 @@ public class WifiConfigController2 implements TextWatcher,
// work done in afterTextChanged
}
/* TODO: Add more test cases for this TextWatcher b/186368002
* This TextWatcher is for IP config fields (Gateway/Network Prefix Length/DNS1) to prevent
* to rewrite the value in these columns that the user wanted to change after they saved.
* When afterTextChanged we will check the text is empty or not then set the Hint for user.
*/
private TextWatcher getIpConfigFieldsTextWatcher(final TextView view) {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// work done in afterTextChanged
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// work done in afterTextChanged
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() == 0) {
if (view.getId() == R.id.gateway) {
mGatewayView.setHint(R.string.wifi_gateway_hint);
} else if (view.getId() == R.id.network_prefix_length) {
mNetworkPrefixLengthView.setHint(R.string.wifi_network_prefix_length_hint);
} else if (view.getId() == R.id.dns1) {
mDns1View.setHint(R.string.wifi_dns1_hint);
}
Button submit = mConfigUi.getSubmitButton();
if (submit == null) return;
submit.setEnabled(false);
} else {
ThreadUtils.postOnMainThread(() -> {
showWarningMessagesIfAppropriate();
enableSubmitIfAppropriate();
});
}
}
};
}
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (textView == mPasswordView) {

View File

@@ -144,31 +144,8 @@ public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements
}
break;
case Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI:
final Uri uri = intent.getData();
final String uriString = (uri == null) ? null : uri.toString();
mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(uriString);
mWifiDppRemoteBandSupport = intent.getIntArrayExtra(
Settings.EXTRA_EASY_CONNECT_BAND_LIST); // returns null if none
final boolean isDppSupported = WifiDppUtils.isWifiDppEnabled(this);
if (!isDppSupported) {
Log.e(TAG,
"ACTION_PROCESS_WIFI_EASY_CONNECT_URI for a device that doesn't "
+ "support Wifi DPP - use WifiManager#isEasyConnectSupported");
}
if (mWifiDppQrCode == null) {
Log.e(TAG, "ACTION_PROCESS_WIFI_EASY_CONNECT_URI with null URI!");
}
if (mWifiDppQrCode == null || !isDppSupported) {
cancelActivity = true;
} else {
final WifiNetworkConfig connectedConfig = getConnectedWifiNetworkConfigOrNull();
if (connectedConfig == null || !connectedConfig.isSupportWifiDpp(this)) {
showChooseSavedWifiNetworkFragment(/* addToBackStack */ false);
} else {
mWifiNetworkConfig = connectedConfig;
showAddDeviceFragment(/* addToBackStack */ false);
}
}
WifiDppUtils.showLockScreen(this,
() -> handleActionProcessWifiEasyConnectUriIntent(intent));
break;
default:
cancelActivity = true;
@@ -180,6 +157,34 @@ public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements
}
}
private void handleActionProcessWifiEasyConnectUriIntent(Intent intent) {
final Uri uri = intent.getData();
final String uriString = (uri == null) ? null : uri.toString();
mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(uriString);
mWifiDppRemoteBandSupport = intent.getIntArrayExtra(
Settings.EXTRA_EASY_CONNECT_BAND_LIST); // returns null if none
final boolean isDppSupported = WifiDppUtils.isWifiDppEnabled(this);
if (!isDppSupported) {
Log.e(TAG,
"ACTION_PROCESS_WIFI_EASY_CONNECT_URI for a device that doesn't "
+ "support Wifi DPP - use WifiManager#isEasyConnectSupported");
}
if (mWifiDppQrCode == null) {
Log.e(TAG, "ACTION_PROCESS_WIFI_EASY_CONNECT_URI with null URI!");
}
if (mWifiDppQrCode == null || !isDppSupported) {
finish();
} else {
final WifiNetworkConfig connectedConfig = getConnectedWifiNetworkConfigOrNull();
if (connectedConfig == null || !connectedConfig.isSupportWifiDpp(this)) {
showChooseSavedWifiNetworkFragment(/* addToBackStack */ false);
} else {
mWifiNetworkConfig = connectedConfig;
showAddDeviceFragment(/* addToBackStack */ false);
}
}
}
private void showQrCodeScannerFragment() {
WifiDppQrCodeScannerFragment fragment =
(WifiDppQrCodeScannerFragment) mFragmentManager.findFragmentByTag(

View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2021 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.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.companion.Association;
import android.companion.CompanionDeviceManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@RunWith(RobolectricTestRunner.class)
public class BluetoothDetailsCompanionAppsControllerTest extends
BluetoothDetailsControllerTestBase {
private static final String PACKAGE_NAME_ONE = "com.google.android.deskclock";
private static final String PACKAGE_NAME_TWO = "com.google.android.calculator";
private static final String PACKAGE_NAME_THREE = "com.google.android.GoogleCamera";
private static final CharSequence APP_NAME_ONE = "deskclock";
private static final CharSequence APP_NAME_TWO = "calculator";
private static final CharSequence APP_NAME_THREE = "GoogleCamera";
@Mock
private CompanionDeviceManager mCompanionDeviceManager;
@Mock
private PackageManager mPackageManager;
private BluetoothDetailsCompanionAppsController mController;
private PreferenceCategory mProfiles;
private List<String> mPackages;
private List<CharSequence> mAppNames;
private List<Association> mAssociations;
@Override
public void setUp() {
super.setUp();
mPackages = Arrays.asList(PACKAGE_NAME_ONE, PACKAGE_NAME_TWO, PACKAGE_NAME_THREE);
mAppNames = Arrays.asList(APP_NAME_ONE, APP_NAME_TWO, APP_NAME_THREE);
mProfiles = spy(new PreferenceCategory(mContext));
mAssociations = new ArrayList<>();
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations);
when(mProfiles.getPreferenceManager()).thenReturn(mPreferenceManager);
setupDevice(mDeviceConfig);
mController =
new BluetoothDetailsCompanionAppsController(mContext, mFragment, mCachedDevice,
mLifecycle);
mController.mCompanionDeviceManager = mCompanionDeviceManager;
mController.mPackageManager = mPackageManager;
mController.mProfilesContainer = mProfiles;
mProfiles.setKey(mController.getPreferenceKey());
mScreen.addPreference(mProfiles);
}
private void setupFakeLabelAndInfo(String packageName, CharSequence appName) {
ApplicationInfo appInfo = mock(ApplicationInfo.class);
try {
when(mPackageManager.getApplicationInfo(packageName, 0)).thenReturn(appInfo);
when(mPackageManager.getApplicationLabel(appInfo)).thenReturn(appName);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
private void addFakeAssociation(String packageName, CharSequence appName) {
setupFakeLabelAndInfo(packageName, appName);
Association association = new Association(
0, mCachedDevice.getAddress(), packageName, "", true, System.currentTimeMillis());
mAssociations.add(association);
showScreen(mController);
}
private Preference getPreference(int index) {
PreferenceCategory preferenceCategory = mProfiles.findPreference(
mController.getPreferenceKey());
return Objects.requireNonNull(preferenceCategory).getPreference(index);
}
private void removeAssociation(String packageName) {
mAssociations = mAssociations.stream()
.filter(a -> !a.getPackageName().equals(packageName))
.collect(Collectors.toList());
when(mCompanionDeviceManager.getAllAssociations()).thenReturn(mAssociations);
showScreen(mController);
}
@Test
public void addOneAssociation_preferenceShouldBeAdded() {
addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE);
Preference preferenceOne = getPreference(0);
assertThat(preferenceOne.getClass()).isEqualTo(CompanionAppWidgetPreference.class);
assertThat(preferenceOne.getKey()).isEqualTo(PACKAGE_NAME_ONE);
assertThat(preferenceOne.getTitle()).isEqualTo(APP_NAME_ONE.toString());
assertThat(mProfiles.getPreferenceCount()).isEqualTo(1);
removeAssociation(PACKAGE_NAME_ONE);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(0);
}
@Test
public void removeOneAssociation_preferenceShouldBeRemoved() {
addFakeAssociation(PACKAGE_NAME_ONE, APP_NAME_ONE);
removeAssociation(PACKAGE_NAME_ONE);
assertThat(mProfiles.getPreferenceCount()).isEqualTo(0);
}
@Test
public void addMultipleAssociations_preferencesShouldBeAdded() {
for (int i = 0; i < mPackages.size(); i++) {
addFakeAssociation(mPackages.get(i), mAppNames.get(i));
Preference preference = getPreference(i);
assertThat(preference.getClass()).isEqualTo(CompanionAppWidgetPreference.class);
assertThat(preference.getKey()).isEqualTo(mPackages.get(i));
assertThat(preference.getTitle()).isEqualTo(mAppNames.get(i).toString());
assertThat(mProfiles.getPreferenceCount()).isEqualTo(i + 1);
}
}
@Test
public void removeMultipleAssociations_preferencesShouldBeRemoved() {
for (int i = 0; i < mPackages.size(); i++) {
addFakeAssociation(mPackages.get(i), mAppNames.get(i).toString());
}
for (int i = 0; i < mPackages.size(); i++) {
removeAssociation(mPackages.get(i));
assertThat(mProfiles.getPreferenceCount()).isEqualTo(mPackages.size() - i - 1);
if (i == mPackages.size() - 1) {
break;
}
assertThat(getPreference(0).getKey()).isEqualTo(mPackages.get(i + 1));
assertThat(getPreference(0).getTitle()).isEqualTo(mAppNames.get(i + 1).toString());
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2021 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.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
import android.view.View;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAlertDialogCompat.class})
public class CompanionAppWidgetPreferenceTest {
private static final String TITLE_ONE = "Test Title 1";
private static final String TITLE_TWO = "Test Title 1";
private static final String KEY_ONE = "Test Key 1";
private static final String KEY_TWO = "Test Key 1";
private Context mContext;
private Drawable mWidgetIconOne;
private Drawable mWidgetIconTwo;
private Drawable mAppIconOne;
private Drawable mAppIconTwo;
@Mock
private View.OnClickListener mWidgetListenerOne;
@Mock
private View.OnClickListener mWidgetListenerTwo;
private List<CompanionAppWidgetPreference> mPreferenceContainer;
@Before
public void setUp() {
mPreferenceContainer = new ArrayList<>();
Context context = spy(RuntimeEnvironment.application.getApplicationContext());
mContext = new ContextThemeWrapper(context, R.style.Theme_Settings);
mWidgetIconOne = mock(Drawable.class);
mAppIconOne = mock(Drawable.class);
mWidgetListenerOne = mock(View.OnClickListener.class);
mWidgetIconTwo = mock(Drawable.class);
mAppIconTwo = mock(Drawable.class);
mWidgetListenerTwo = mock(View.OnClickListener.class);
}
private void setUpPreferenceContainer(Drawable widgetIcon, Drawable appIcon,
View.OnClickListener listener, String title, String key) {
CompanionAppWidgetPreference preference = new CompanionAppWidgetPreference(
widgetIcon, listener, mContext);
preference.setIcon(appIcon);
preference.setTitle(title);
preference.setKey(key);
mPreferenceContainer.add(preference);
}
@Test
public void setUpPreferenceContainer_preferenceShouldBeAdded() {
setUpPreferenceContainer(
mWidgetIconOne, mAppIconOne, mWidgetListenerOne, TITLE_ONE, KEY_ONE);
assertThat(mPreferenceContainer.get(0).getIcon()).isEqualTo(mAppIconOne);
assertThat(mPreferenceContainer.get(0).getKey()).isEqualTo(KEY_ONE);
assertThat(mPreferenceContainer.get(0).getTitle()).isEqualTo(TITLE_ONE);
setUpPreferenceContainer(
mWidgetIconTwo, mAppIconTwo, mWidgetListenerTwo, TITLE_TWO, KEY_TWO);
assertThat(mPreferenceContainer.get(1).getIcon()).isEqualTo(mAppIconTwo);
assertThat(mPreferenceContainer.get(1).getKey()).isEqualTo(KEY_TWO);
assertThat(mPreferenceContainer.get(1).getTitle()).isEqualTo(TITLE_TWO);
}
}

View File

@@ -47,6 +47,7 @@ import androidx.loader.app.LoaderManager;
import androidx.preference.Preference;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowActivityManager;
@@ -82,6 +83,7 @@ public class AdvancedPowerUsageDetailTest {
private static final String USAGE_PERCENT = "16%";
private static final int ICON_ID = 123;
private static final int UID = 1;
private static final int POWER_MAH = 150;
private static final long BACKGROUND_TIME_MS = 100;
private static final long FOREGROUND_ACTIVITY_TIME_MS = 123;
private static final long FOREGROUND_SERVICE_TIME_MS = 444;
@@ -117,6 +119,8 @@ public class AdvancedPowerUsageDetailTest {
@Mock
private BatteryOptimizeUtils mBatteryOptimizeUtils;
private Context mContext;
private Preference mForegroundPreference;
private Preference mBackgroundPreference;
private Preference mFooterPreference;
private RadioButtonPreference mRestrictedPreference;
private RadioButtonPreference mOptimizePreference;
@@ -166,6 +170,7 @@ public class AdvancedPowerUsageDetailTest {
mFragment.mHeaderPreference = mHeaderPreference;
mFragment.mState = mState;
mFragment.enableTriState = true;
mFragment.mBatteryUtils = new BatteryUtils(RuntimeEnvironment.application);
mFragment.mBatteryOptimizeUtils = mBatteryOptimizeUtils;
mAppEntry.info = mock(ApplicationInfo.class);
@@ -190,10 +195,14 @@ public class AdvancedPowerUsageDetailTest {
nullable(UserHandle.class));
doAnswer(callable).when(mActivity).startActivity(captor.capture());
mForegroundPreference = new Preference(mContext);
mBackgroundPreference = new Preference(mContext);
mFooterPreference = new Preference(mContext);
mRestrictedPreference = new RadioButtonPreference(mContext);
mOptimizePreference = new RadioButtonPreference(mContext);
mUnrestrictedPreference = new RadioButtonPreference(mContext);
mFragment.mForegroundPreference = mForegroundPreference;
mFragment.mBackgroundPreference = mBackgroundPreference;
mFragment.mFooterPreference = mFooterPreference;
mFragment.mRestrictedPreference = mRestrictedPreference;
mFragment.mOptimizePreference = mOptimizePreference;
@@ -205,6 +214,17 @@ public class AdvancedPowerUsageDetailTest {
ShadowEntityHeaderController.reset();
}
@Test
public void testGetPreferenceScreenResId_returnNewLayout() {
assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail);
}
@Test
public void testGetPreferenceScreenResId_disableTriState_returnLegacyLayout() {
mFragment.enableTriState = false;
assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail_legacy);
}
@Test
public void testInitHeader_NoAppEntry_BuildByBundle() {
mFragment.mAppEntry = null;
@@ -455,32 +475,52 @@ public class AdvancedPowerUsageDetailTest {
}
@Test
public void testInitPreference_isValidPackageName_hasCorrectString() {
public void testInitPreference_hasCorrectSummary() {
Bundle bundle = new Bundle(4);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, BACKGROUND_TIME_MS);
bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, FOREGROUND_TIME_MS);
bundle.putString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT, USAGE_PERCENT);
bundle.putInt(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_AMOUNT, POWER_MAH);
when(mFragment.getArguments()).thenReturn(bundle);
doReturn(mContext.getText(R.string.battery_used_for)).when(mFragment).getText(
R.string.battery_used_for);
doReturn(mContext.getText(R.string.battery_active_for)).when(mFragment).getText(
R.string.battery_active_for);
mFragment.initPreference(mContext);
assertThat(mForegroundPreference.getSummary().toString()).isEqualTo("Used for 0 min");
assertThat(mBackgroundPreference.getSummary().toString()).isEqualTo("Active for 0 min");
}
@Test
public void testInitPreferenceForTriState_isValidPackageName_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(false);
mFragment.initPreference();
mFragment.initPreferenceForTriState(mContext);
assertThat(mFooterPreference.getTitle().toString())
.isEqualTo("This app requires Optimized battery usage.");
}
@Test
public void testInitPreference_isSystemOrDefaultApp_hasCorrectString() {
public void testInitPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true);
mFragment.initPreference();
mFragment.initPreferenceForTriState(mContext);
assertThat(mFooterPreference.getTitle()
.toString()).isEqualTo("This app requires Unrestricted battery usage.");
}
@Test
public void testInitPreference_hasCorrectString() {
public void testInitPreferenceForTriState_hasCorrectString() {
when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false);
mFragment.initPreference();
mFragment.initPreferenceForTriState(mContext);
assertThat(mFooterPreference.getTitle().toString())
.isEqualTo("Changing how an app uses your battery can affect its performance.");