Replace battery usage breakdown list from tabs to dropdown list.

screen records:
https://drive.google.com/file/d/15VJGQ_G2KIpyFcvZsyE0iRno0WZhfjGb/view?usp=sharing&resourcekey=0-bg48BsC2b-BT_80CGlzpWg
https://drive.google.com/file/d/1RaoysytQ5oZQu8CNPhYgxqBahk3UfVbr/view?usp=sharing&resourcekey=0-Xk2J36fjYWBo6KpFTrMr4Q

Bug: 264338267
Fix: 264338267
Test: manual
Change-Id: Ic020cce711b5232adfc80272836d7c2d0250d94a
This commit is contained in:
Zaiyue Xue
2023-01-04 14:10:22 +08:00
parent a50fb6ec33
commit 778a4b42e3
7 changed files with 186 additions and 227 deletions

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Copyright (C) 2022 The Android Open Source Project ~ Copyright (C) 2023 The Android Open Source Project
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License. ~ you may not use this file except in compliance with the License.
@@ -15,23 +14,11 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<LinearLayout <Spinner
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:theme="@style/Theme.TabTheme" android:id="@+id/spinner"
android:id="@+id/tab_container" android:layout_width="wrap_content"
android:clipToPadding="true"
android:clipChildren="true"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
<com.google.android.material.tabs.TabLayout android:theme="@style/Widget.PopupWindow.Settings" />
android:id="@+id/tabs"
style="@style/SettingsLibTabsStyle" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -5175,13 +5175,13 @@
<!-- [CHAR_LIMIT=NONE] Accessibility content description for hourly battery chart view. --> <!-- [CHAR_LIMIT=NONE] Accessibility content description for hourly battery chart view. -->
<string name="hourly_battery_usage_chart">Hourly battery usage chart</string> <string name="hourly_battery_usage_chart">Hourly battery usage chart</string>
<!-- [CHAR_LIMIT=NONE] Battery usage breakdown title since last full charge --> <!-- [CHAR_LIMIT=NONE] Battery usage breakdown title since last full charge -->
<string name="battery_usage_breakdown_title_since_last_full_charge">Usage proportional breakdown since last full charge</string> <string name="battery_usage_breakdown_title_since_last_full_charge">Battery usage since last full charge</string>
<!-- [CHAR_LIMIT=NONE] Battery usage breakdown title for a selected slot --> <!-- [CHAR_LIMIT=NONE] Battery usage breakdown title for a selected slot -->
<string name="battery_usage_breakdown_title_for_slot">Usage proportional breakdown for <xliff:g id="slot">%s</xliff:g></string> <string name="battery_usage_breakdown_title_for_slot">Battery usage for <xliff:g id="slot">%s</xliff:g></string>
<!-- [CHAR_LIMIT=NONE] The tab title in the battery usage breakdown. --> <!-- [CHAR_LIMIT=NONE] The spinner item text in the battery usage breakdown. -->
<string name="battery_usage_app_tab">App</string> <string name="battery_usage_spinner_breakdown_by_apps">Breakdown by apps</string>
<!-- [CHAR_LIMIT=NONE] The tab title in the battery usage breakdown. --> <!-- [CHAR_LIMIT=NONE] The spinner item text in the battery usage breakdown. -->
<string name="battery_usage_system_tab">System</string> <string name="battery_usage_spinner_breakdown_by_system">Breakdown by system</string>
<!-- Process Stats strings --> <!-- Process Stats strings -->
<skip /> <skip />

View File

@@ -32,8 +32,8 @@
"com.android.settings.fuelgauge.batteryusage.BatteryUsageBreakdownController" "com.android.settings.fuelgauge.batteryusage.BatteryUsageBreakdownController"
settings:isPreferenceVisible="false"> settings:isPreferenceVisible="false">
<com.android.settings.fuelgauge.batteryusage.TabPreference <com.android.settings.fuelgauge.batteryusage.SpinnerPreference
android:key="battery_usage_tab" android:key="battery_usage_spinner"
settings:isPreferenceVisible="false" /> settings:isPreferenceVisible="false" />
<PreferenceCategory <PreferenceCategory

View File

@@ -24,12 +24,13 @@ import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.viewpager2.widget.ViewPager2;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
@@ -55,7 +56,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
private static final String TAG = "BatteryUsageBreakdownController"; private static final String TAG = "BatteryUsageBreakdownController";
private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown"; private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown";
private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer"; private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer";
private static final String TAB_PREFERENCE_KEY = "battery_usage_tab"; private static final String SPINNER_PREFERENCE_KEY = "battery_usage_spinner";
private static final String APP_LIST_PREFERENCE_KEY = "app_list"; private static final String APP_LIST_PREFERENCE_KEY = "app_list";
private static final String PACKAGE_NAME_NONE = "none"; private static final String PACKAGE_NAME_NONE = "none";
private static final int ENABLED_ICON_ALPHA = 255; private static final int ENABLED_ICON_ALPHA = 255;
@@ -69,7 +70,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
@VisibleForTesting @VisibleForTesting
final Map<String, Preference> mPreferenceCache = new HashMap<>(); final Map<String, Preference> mPreferenceCache = new HashMap<>();
private int mTabPosition; private int mSpinnerPosition;
private String mSlotTimestamp; private String mSlotTimestamp;
@VisibleForTesting @VisibleForTesting
@@ -77,7 +78,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
@VisibleForTesting @VisibleForTesting
PreferenceCategory mRootPreference; PreferenceCategory mRootPreference;
@VisibleForTesting @VisibleForTesting
TabPreference mTabPreference; SpinnerPreference mSpinnerPreference;
@VisibleForTesting @VisibleForTesting
PreferenceGroup mAppListPreferenceGroup; PreferenceGroup mAppListPreferenceGroup;
@VisibleForTesting @VisibleForTesting
@@ -145,26 +146,33 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
super.displayPreference(screen); super.displayPreference(screen);
mPrefContext = screen.getContext(); mPrefContext = screen.getContext();
mRootPreference = screen.findPreference(ROOT_PREFERENCE_KEY); mRootPreference = screen.findPreference(ROOT_PREFERENCE_KEY);
mTabPreference = screen.findPreference(TAB_PREFERENCE_KEY); mSpinnerPreference = screen.findPreference(SPINNER_PREFERENCE_KEY);
mAppListPreferenceGroup = screen.findPreference(APP_LIST_PREFERENCE_KEY); mAppListPreferenceGroup = screen.findPreference(APP_LIST_PREFERENCE_KEY);
mFooterPreference = screen.findPreference(FOOTER_PREFERENCE_KEY); mFooterPreference = screen.findPreference(FOOTER_PREFERENCE_KEY);
mAppListPreferenceGroup.setOrderingAsAdded(false); mAppListPreferenceGroup.setOrderingAsAdded(false);
mTabPreference.initializeTabs(mFragment, new String[]{ mSpinnerPreference.initializeSpinner(
mPrefContext.getString(R.string.battery_usage_app_tab), new String[]{
mPrefContext.getString(R.string.battery_usage_system_tab) mPrefContext.getString(R.string.battery_usage_spinner_breakdown_by_apps),
}); mPrefContext.getString(R.string.battery_usage_spinner_breakdown_by_system)
mTabPreference.setOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { },
@Override new AdapterView.OnItemSelectedListener() {
public void onPageSelected(int position) { @Override
super.onPageSelected(position); public void onItemSelected(
mTabPosition = position; AdapterView<?> parent, View view, int position, long id) {
mHandler.post(() -> { if (mSpinnerPosition != position) {
removeAndCacheAllPreferences(); mSpinnerPosition = position;
addAllPreferences(); mHandler.post(() -> {
removeAndCacheAllPreferences();
addAllPreferences();
});
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}); });
}
});
} }
/** /**
@@ -182,7 +190,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
mSlotTimestamp = slotTimestamp; mSlotTimestamp = slotTimestamp;
showCategoryTitle(slotTimestamp); showCategoryTitle(slotTimestamp);
showTabAndAppList(); showSpinnerAndAppList();
showFooterPreference(isAllUsageDataEmpty); showFooterPreference(isAllUsageDataEmpty);
} }
@@ -204,12 +212,12 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
mFooterPreference.setVisible(true); mFooterPreference.setVisible(true);
} }
private void showTabAndAppList() { private void showSpinnerAndAppList() {
removeAndCacheAllPreferences(); removeAndCacheAllPreferences();
if (mBatteryDiffData == null) { if (mBatteryDiffData == null) {
return; return;
} }
mTabPreference.setVisible(true); mSpinnerPreference.setVisible(true);
mAppListPreferenceGroup.setVisible(true); mAppListPreferenceGroup.setVisible(true);
mHandler.post(() -> { mHandler.post(() -> {
addAllPreferences(); addAllPreferences();
@@ -222,7 +230,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
return; return;
} }
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
final List<BatteryDiffEntry> entries = mTabPosition == 0 final List<BatteryDiffEntry> entries = mSpinnerPosition == 0
? mBatteryDiffData.getAppDiffEntryList() ? mBatteryDiffData.getAppDiffEntryList()
: mBatteryDiffData.getSystemDiffEntryList(); : mBatteryDiffData.getSystemDiffEntryList();
int prefIndex = mAppListPreferenceGroup.getPreferenceCount(); int prefIndex = mAppListPreferenceGroup.getPreferenceCount();

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2023 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.fuelgauge.batteryusage;
import android.content.Context;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.AdapterView;
import android.widget.Spinner;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settingslib.widget.SettingsSpinnerAdapter;
/** A preference which contains a spinner. */
public class SpinnerPreference extends Preference {
private static final String TAG = "SpinnerPreference";
private AdapterView.OnItemSelectedListener mOnItemSelectedListener;
@VisibleForTesting
Spinner mSpinner;
@VisibleForTesting
String[] mItems;
@VisibleForTesting
int mSavedSpinnerPosition;
public SpinnerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.preference_spinner);
}
void initializeSpinner(
String[] items, AdapterView.OnItemSelectedListener onItemSelectedListener) {
mItems = items;
mOnItemSelectedListener = onItemSelectedListener;
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
mSpinner = (Spinner) view.findViewById(R.id.spinner);
mSpinner.setAdapter(new SpinnerAdapter(getContext(), mItems));
mSpinner.setSelection(mSavedSpinnerPosition);
if (mOnItemSelectedListener != null) {
mSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
}
}
@Override
protected Parcelable onSaveInstanceState() {
Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinner.getSelectedItemPosition());
return new SavedState(super.onSaveInstanceState(), mSpinner.getSelectedItemPosition());
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mSavedSpinnerPosition = savedState.getSpinnerPosition();
if (mOnItemSelectedListener != null) {
mOnItemSelectedListener.onItemSelected(/* parent= */null, /* view= */null,
savedState.getSpinnerPosition(), /* id= */ 0);
}
Log.d(TAG, "onRestoreInstanceState() spinnerPosition=" + savedState.getSpinnerPosition());
}
@VisibleForTesting
static class SavedState extends BaseSavedState {
private int mSpinnerPosition;
SavedState(Parcelable superState, int spinnerPosition) {
super(superState);
mSpinnerPosition = spinnerPosition;
}
int getSpinnerPosition() {
return mSpinnerPosition;
}
}
private static class SpinnerAdapter extends SettingsSpinnerAdapter<CharSequence> {
private final String[] mItems;
SpinnerAdapter(Context context, String[] items) {
super(context);
mItems = items;
}
@Override
public int getCount() {
return mItems.length;
}
@Override
public CharSequence getItem(int position) {
return mItems[position];
}
}
}

View File

@@ -1,152 +0,0 @@
/*
* Copyright (C) 2022 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.fuelgauge.batteryusage;
import android.content.Context;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
/** A preference which contains a tab selection. */
public class TabPreference extends Preference {
private static final String TAG = "TabPreference";
private Fragment mRootFragment;
private ViewPager2 mViewPager;
private ViewPager2.OnPageChangeCallback mOnPageChangeCallback;
@VisibleForTesting
String[] mTabTitles;
@VisibleForTesting
int mSavedTabPosition;
@VisibleForTesting
TabLayout mTabLayout;
public TabPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.preference_tab);
}
void initializeTabs(Fragment rootFragment, String[] tabTitles) {
mRootFragment = rootFragment;
mTabTitles = tabTitles;
}
void setOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback) {
mOnPageChangeCallback = callback;
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
if (mViewPager != null && mTabLayout != null) {
return;
}
mViewPager = (ViewPager2) view.findViewById(R.id.view_pager);
mViewPager.setAdapter(new FragmentAdapter(mRootFragment, mTabTitles.length));
mViewPager.setUserInputEnabled(false);
if (mOnPageChangeCallback != null) {
mViewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
}
mTabLayout = (TabLayout) view.findViewById(R.id.tabs);
new TabLayoutMediator(
mTabLayout, mViewPager, /* autoRefresh= */ true, /* smoothScroll= */ false,
(tab, position) -> tab.setText(mTabTitles[position])).attach();
mTabLayout.getTabAt(mSavedTabPosition).select();
}
@Override
public void onDetached() {
super.onDetached();
if (mViewPager != null && mOnPageChangeCallback != null) {
mViewPager.unregisterOnPageChangeCallback(mOnPageChangeCallback);
}
}
@Override
protected Parcelable onSaveInstanceState() {
Log.d(TAG, "onSaveInstanceState() tabPosition=" + mTabLayout.getSelectedTabPosition());
return new SavedState(super.onSaveInstanceState(), mTabLayout.getSelectedTabPosition());
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mSavedTabPosition = savedState.getTabPosition();
Log.d(TAG, "onRestoreInstanceState() tabPosition=" + savedState.getTabPosition());
}
@VisibleForTesting
static class SavedState extends BaseSavedState {
private int mTabPosition;
SavedState(Parcelable superState, int tabPosition) {
super(superState);
mTabPosition = tabPosition;
}
int getTabPosition() {
return mTabPosition;
}
}
private static class FragmentAdapter extends FragmentStateAdapter {
private final int mItemCount;
private final Fragment[] mItemFragments;
FragmentAdapter(@NonNull Fragment rootFragment, int itemCount) {
super(rootFragment);
mItemCount = itemCount;
mItemFragments = new Fragment[mItemCount];
for (int i = 0; i < mItemCount; i++) {
// Empty tab pages.
mItemFragments[i] = new Fragment();
}
}
@NonNull
@Override
public Fragment createFragment(int position) {
return mItemFragments[position];
}
@Override
public int getItemCount() {
return mItemCount;
}
}
}

View File

@@ -22,14 +22,12 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import android.content.Context; import android.content.Context;
import android.widget.Spinner;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference; import androidx.preference.Preference;
import com.android.settings.R; import com.android.settings.R;
import com.google.android.material.tabs.TabLayout;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -39,50 +37,47 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public final class TabPreferenceTest { public final class SpinnerPreferenceTest {
private Context mContext; private Context mContext;
private TabPreference mTabPreference; private SpinnerPreference mSpinnerPreference;
@Mock @Mock
private Fragment mMockFragment; private Spinner mMockSpinner;
@Mock
private TabLayout mMockTabLayout;
private final String[] mTabTitles = new String[]{"tab1", "tab2"};
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application); mContext = spy(RuntimeEnvironment.application);
mTabPreference = new TabPreference(mContext, /*attrs=*/ null); mSpinnerPreference = new SpinnerPreference(mContext, /*attrs=*/ null);
} }
@Test @Test
public void constructor_returnExpectedResult() { public void constructor_returnExpectedResult() {
assertThat(mTabPreference.getLayoutResource()).isEqualTo(R.layout.preference_tab); assertThat(mSpinnerPreference.getLayoutResource()).isEqualTo(R.layout.preference_spinner);
} }
@Test @Test
public void initializeTabs_returnExpectedResult() { public void initializeSpinner_returnExpectedResult() {
mTabPreference.initializeTabs(mMockFragment, mTabTitles); final String[] items = new String[]{"item1", "item2"};
assertThat(mTabPreference.mTabTitles).isEqualTo(mTabTitles); mSpinnerPreference.initializeSpinner(items, null);
assertThat(mSpinnerPreference.mItems).isEqualTo(items);
} }
@Test @Test
public void onSaveInstanceState_returnExpectedResult() { public void onSaveInstanceState_returnExpectedResult() {
doReturn(1).when(mMockTabLayout).getSelectedTabPosition(); doReturn(1).when(mMockSpinner).getSelectedItemPosition();
mTabPreference.mTabLayout = mMockTabLayout; mSpinnerPreference.mSpinner = mMockSpinner;
TabPreference.SavedState savedState = SpinnerPreference.SavedState savedState =
(TabPreference.SavedState) mTabPreference.onSaveInstanceState(); (SpinnerPreference.SavedState) mSpinnerPreference.onSaveInstanceState();
assertThat(savedState.getTabPosition()).isEqualTo(1); assertThat(savedState.getSpinnerPosition()).isEqualTo(1);
} }
@Test @Test
public void onRestoreInstanceState_returnExpectedResult() { public void onRestoreInstanceState_returnExpectedResult() {
TabPreference.SavedState savedState = SpinnerPreference.SavedState savedState =
new TabPreference.SavedState(Preference.BaseSavedState.EMPTY_STATE, 2); new SpinnerPreference.SavedState(Preference.BaseSavedState.EMPTY_STATE, 2);
mTabPreference.onRestoreInstanceState(savedState); mSpinnerPreference.onRestoreInstanceState(savedState);
assertThat(mTabPreference.mSavedTabPosition).isEqualTo(2); assertThat(mSpinnerPreference.mSavedSpinnerPosition).isEqualTo(2);
} }
} }