From 06af9f9005444892eacfbdbd6d59546c9bbc7690 Mon Sep 17 00:00:00 2001 From: Timi Date: Mon, 8 Mar 2021 20:36:51 +0100 Subject: [PATCH 01/26] Device picker: add title to fix offset in progressbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a title to device picker fragment in order to move the progressbar to the correct place Test: make Settings, share a file via Bluetooth, verify progressbar is properly placed Change-Id: I2fcf293ed2d70d123d65cf76051a6d7d745a773b Signed-off-by: Timi Rautamäki --- res/xml/device_picker.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/xml/device_picker.xml b/res/xml/device_picker.xml index fd043a280de..6f8d267cd66 100644 --- a/res/xml/device_picker.xml +++ b/res/xml/device_picker.xml @@ -19,7 +19,8 @@ + android:orderingFromXml="false" + android:title="@string/bluetooth_preference_found_media_devices" /> From 27eec612891f2b5e36914b4604e696d6a0c4d35e Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Wed, 5 May 2021 22:30:59 +0800 Subject: [PATCH 02/26] Add divider below ActionButtonPreference in BluetoothDetailFragment Bug: 187121773 Test: visual Change-Id: I230967ae807431d92dcb82c16b12891a49b3ee0e --- res/xml/bluetooth_device_details_fragment.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index 5084d4d481a..9df195584d6 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -38,6 +38,11 @@ android:key="action_buttons" settings:allowDividerBelow="true"/> + + + Date: Wed, 28 Apr 2021 08:54:29 +0000 Subject: [PATCH 03/26] Redirect battery-level settings preference clicks if needed Change-Id: I64d0992544cc7e0cb41293b799577cbc43dfbb38 Bug: 181827923 Test: unit --- .../TopLevelBatteryPreferenceController.java | 56 +++++++++ ...pLevelBatteryPreferenceControllerTest.java | 107 +++++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java index 99eb05d8116..2eb7cf413fc 100644 --- a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java +++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java @@ -16,7 +16,9 @@ package com.android.settings.fuelgauge; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -29,6 +31,8 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import java.util.HashMap; + public class TopLevelBatteryPreferenceController extends BasePreferenceController implements LifecycleObserver, OnStart, OnStop, BatteryPreferenceController { @@ -37,9 +41,13 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; private Preference mPreference; private BatteryInfo mBatteryInfo; + private BatterySettingsFeatureProvider mBatterySettingsFeatureProvider; private BatteryStatusFeatureProvider mBatteryStatusFeatureProvider; private String mBatteryStatusLabel; + @VisibleForTesting + protected static HashMap sReplacingActivityMap = new HashMap<>(); + public TopLevelBatteryPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); @@ -53,6 +61,8 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle }, true /* shortString */); }); + mBatterySettingsFeatureProvider = FeatureFactory.getFactory(context) + .getBatterySettingsFeatureProvider(context); mBatteryStatusFeatureProvider = FeatureFactory.getFactory(context) .getBatteryStatusFeatureProvider(context); } @@ -69,6 +79,37 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle mPreference = screen.findPreference(getPreferenceKey()); } + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + String prefFrag = preference.getFragment(); + if (prefFrag == null || prefFrag.isEmpty()) { + // Not a redirect, so use the default. + return super.handlePreferenceTreeClick(preference); + } + + ComponentName currentFragmentName = convertClassPathToComponentName(prefFrag); + if (currentFragmentName == null) { + return super.handlePreferenceTreeClick(preference); + } + + ComponentName replacingActivity; + if (sReplacingActivityMap.containsKey(prefFrag)) { + replacingActivity = sReplacingActivityMap.get(prefFrag); + } else { + replacingActivity = mBatterySettingsFeatureProvider.getReplacingActivity( + currentFragmentName); + sReplacingActivityMap.put(prefFrag, replacingActivity); + } + + if (replacingActivity == null || currentFragmentName.compareTo(replacingActivity) == 0) { + return super.handlePreferenceTreeClick(preference); + } + Intent intent = new Intent(); + intent.setComponent(currentFragmentName); + mContext.startActivity(intent); + return true; + } + @Override public void onStart() { mBatteryBroadcastReceiver.register(); @@ -133,4 +174,19 @@ public class TopLevelBatteryPreferenceController extends BasePreferenceControlle } } } + + @VisibleForTesting + protected static ComponentName convertClassPathToComponentName(String classPath) { + if (classPath == null || classPath.isEmpty()) { + return null; + } + String[] split = classPath.split("\\."); + int classNameIndex = split.length - 1; + if (classNameIndex < 0) { + return null; + } + int lastPkgIndex = classPath.length() - split[classNameIndex].length() - 1; + String pkgName = lastPkgIndex > 0 ? classPath.substring(0, lastPkgIndex) : ""; + return new ComponentName(pkgName, split[classNameIndex]); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java index 1a3c98f8ba1..af4ab0f9198 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java @@ -21,26 +21,55 @@ import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_ import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.ComponentName; import android.content.Context; +import android.util.FeatureFlagUtils; + +import androidx.preference.Preference; import com.android.settings.R; +import com.android.settings.core.FeatureFlags; +import com.android.settings.testutils.FakeFeatureFactory; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) public class TopLevelBatteryPreferenceControllerTest { private Context mContext; + private FakeFeatureFactory mFeatureFactory; private TopLevelBatteryPreferenceController mController; + private BatterySettingsFeatureProvider mBatterySettingsFeatureProvider; @Before public void setUp() { - mContext = RuntimeEnvironment.application; + MockitoAnnotations.initMocks(this); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mContext = spy(Robolectric.setupActivity(Activity.class)); mController = new TopLevelBatteryPreferenceController(mContext, "test_key"); + mBatterySettingsFeatureProvider = + mFeatureFactory.batterySettingsFeatureProvider; + FeatureFlagUtils.setEnabled(mContext, FeatureFlags.SILKY_HOME, false); + } + + @After + public void cleanUp() { + TopLevelBatteryPreferenceController.sReplacingActivityMap.clear(); } @Test @@ -54,6 +83,80 @@ public class TopLevelBatteryPreferenceControllerTest { assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } + @Test + public void handlePreferenceTreeClick_noFragment_noCustomActivityCalled() { + Preference preference = new Preference(mContext); + + assertThat(mController.handlePreferenceTreeClick(preference)).isFalse(); + } + + @Test + public void handlePreferenceTreeClick_sameActivityReturned_noCustomActivityCalled() { + String fragmentPath = "my.fragment.ClassName"; + Preference preference = mock(Preference.class); + when(preference.getFragment()).thenReturn(fragmentPath); + ComponentName pathName = mController.convertClassPathToComponentName(fragmentPath); + when(mBatterySettingsFeatureProvider.getReplacingActivity(any())).thenReturn(pathName); + + assertThat(mController.handlePreferenceTreeClick(preference)).isFalse(); + } + + @Test + public void handlePreferenceTreeClick_newActivityReturned_newActivityRedirected() { + String fragmentPath = "my.fragment.ClassName"; + Preference preference = mock(Preference.class); + when(preference.getFragment()).thenReturn(fragmentPath); + String newFragmentPath = "my.fragment.NewClassName"; + ComponentName newPathName = mController.convertClassPathToComponentName(newFragmentPath); + when(mBatterySettingsFeatureProvider.getReplacingActivity(any())).thenReturn( + newPathName); + doNothing().when(mContext).startActivity(any()); + + assertThat(mController.handlePreferenceTreeClick(preference)).isTrue(); + } + + @Test + public void handlePreferenceTreeClick_calledMultipleTimes_fetchedFromCache() { + String fragmentPath = "my.fragment.ClassName"; + Preference preference = mock(Preference.class); + when(preference.getFragment()).thenReturn(fragmentPath); + String newFragmentPath = "my.fragment.NewClassName"; + ComponentName newPathName = mController.convertClassPathToComponentName(newFragmentPath); + when(mBatterySettingsFeatureProvider.getReplacingActivity(any())).thenReturn( + newPathName); + doNothing().when(mContext).startActivity(any()); + + assertThat(mController.handlePreferenceTreeClick(preference)).isTrue(); + assertThat(mController.handlePreferenceTreeClick(preference)).isTrue(); + verify(mBatterySettingsFeatureProvider, times(1)).getReplacingActivity(any()); + } + + @Test + public void convertClassPathToComponentName_nullInput_returnsNull() { + assertThat(mController.convertClassPathToComponentName(null)).isNull(); + } + + @Test + public void convertClassPathToComponentName_emptyStringInput_returnsNull() { + assertThat(mController.convertClassPathToComponentName("")).isNull(); + } + + @Test + public void convertClassPathToComponentName_singleClassName_returnsCorrectComponentName() { + ComponentName output = mController.convertClassPathToComponentName("ClassName"); + + assertThat(output.getPackageName()).isEqualTo(""); + assertThat(output.getClassName()).isEqualTo("ClassName"); + } + + @Test + public void convertClassPathToComponentName_validAddress_returnsCorrectComponentName() { + ComponentName output = mController.convertClassPathToComponentName("my.fragment.ClassName"); + + assertThat(output.getPackageName()).isEqualTo("my.fragment"); + assertThat(output.getClassName()).isEqualTo("ClassName"); + } + @Test public void getDashboardLabel_returnsCorrectLabel() { BatteryInfo info = new BatteryInfo(); From 90d323cefa927d1f9db76ec7323066d602a42d6b Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Thu, 13 May 2021 20:51:47 +0800 Subject: [PATCH 04/26] Move DWB to the fifth position from the bottom on the homepage Remove relevant configs, logics and tests. Bug: 187885890 Test: robotests & visual Change-Id: I309d5290f19bf0b01692dd6f14c4cdcc52066453 --- res/values/config.xml | 6 ----- .../DashboardFeatureProviderImpl.java | 24 ------------------- tests/robotests/res/values-mcc999/config.xml | 10 -------- .../DashboardFeatureProviderImplTest.java | 14 ----------- 4 files changed, 54 deletions(-) diff --git a/res/values/config.xml b/res/values/config.xml index d37db441ea4..21d9c7528d1 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -495,12 +495,6 @@ - - - - - - false diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index b404ecccdc7..5a686c26a06 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -185,9 +185,6 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { pref.setOrder(order + baseOrder); } } - - overrideTilePosition(tile, pref); - return outObservers.isEmpty() ? null : outObservers; } @@ -457,25 +454,4 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } return eligibleUsers; } - - private void overrideTilePosition(Tile tile, Preference pref) { - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.SILKY_HOME) - && TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) { - final String[] homepageTilePackages = mContext.getResources().getStringArray( - R.array.config_homepage_tile_packages); - final int[] homepageTileOrders = mContext.getResources().getIntArray( - R.array.config_homepage_tile_orders); - if (homepageTilePackages.length == 0 - || homepageTilePackages.length != homepageTileOrders.length) { - return; - } - - for (int i = 0; i < homepageTilePackages.length; i++) { - if (TextUtils.equals(tile.getPackageName(), homepageTilePackages[i])) { - pref.setOrder(homepageTileOrders[i]); - return; - } - } - } - } } diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index af02d4523dc..b1d4be9ea1c 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -100,14 +100,4 @@ content://com.android.settings.slices/test - - - - com.android.settings - - - - - 100 - diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java index 1fba12ba979..b2d11dc8cd0 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java @@ -672,18 +672,4 @@ public class DashboardFeatureProviderImplTest { assertThat(argument.getValue().getIdentifier()).isEqualTo(0); verify(mActivity, never()).getSupportFragmentManager(); } - - @Test - @Config(qualifiers = "mcc999") - public void bindPreference_specificHomepageTile_shouldOverridePosition() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.SILKY_HOME, true); - final Preference preference = new Preference(RuntimeEnvironment.application); - final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, - Preference.DEFAULT_ORDER); - - assertThat(preference.getOrder()).isEqualTo(100); - } } From b7202aa33245ca3760c09ada4c03599db02efe00 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Thu, 13 May 2021 21:39:31 +0800 Subject: [PATCH 05/26] Fix NPE in DrawOverlayDetails#getSummary When AppEntry is null (APP uninstalled), returns an empty summary string. Bug: 172381586 Test: build pass Change-Id: I63fbeaaec6f2d0de617c6e104af341806605a6c2 --- .../settings/applications/appinfo/DrawOverlayDetails.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java index 0f90c69c9ae..5f7e56fa0e4 100644 --- a/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java +++ b/src/com/android/settings/applications/appinfo/DrawOverlayDetails.java @@ -158,6 +158,10 @@ public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenc } public static CharSequence getSummary(Context context, AppEntry entry) { + if (entry == null) { + return ""; + } + OverlayState state; if (entry.extraInfo instanceof OverlayState) { state = (OverlayState) entry.extraInfo; From 11de1a6452d45dc48a78b8784d894dece36e7060 Mon Sep 17 00:00:00 2001 From: ykhung Date: Thu, 13 May 2021 15:35:01 +0800 Subject: [PATCH 06/26] Make chart time slot not clickable when accessibility servie is enabled after discuss with Shuan & Peggy, we will make the chat view is not clickable since accessibility service is enabled for talkback, switch access and voice access, since some gestures are conflict with our design (double click to show all contents is conflict with a11y behavior) Bug: 187814675 Test: make SettingsRoboTests Change-Id: I7eecafc42cf4b4a0374ab46f07461e77907fc03a --- .../settings/fuelgauge/BatteryChartView.java | 41 +++++++++++-- .../fuelgauge/BatteryChartViewTest.java | 60 +++++++++++++++++++ 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryChartView.java b/src/com/android/settings/fuelgauge/BatteryChartView.java index 104801704d1..1590a57818d 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/BatteryChartView.java @@ -24,6 +24,7 @@ import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; +import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; @@ -44,7 +45,8 @@ import java.util.List; import java.util.Locale; /** A widget component to draw chart graph. */ -public class BatteryChartView extends AppCompatImageView implements View.OnClickListener { +public class BatteryChartView extends AppCompatImageView implements View.OnClickListener, + AccessibilityManager.AccessibilityStateChangeListener { private static final String TAG = "BatteryChartView"; private static final List ACCESSIBILITY_SERVICE_NAMES = Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService"); @@ -52,6 +54,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick private static final String[] PERCENTAGES = new String[] {"100%", "50%", "0%"}; private static final int DEFAULT_TRAPEZOID_COUNT = 12; private static final int DEFAULT_TIMESTAMP_COUNT = 4; + private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5"); + private static final long UPDATE_STATE_DELAYED_TIME = 500L; /** Selects all trapezoid shapes. */ public static final int SELECTED_INDEX_ALL = -1; @@ -74,7 +78,6 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick // Colors for drawing the trapezoid shape and dividers. 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(); @@ -85,11 +88,17 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick private final Rect[] mTimestampsBounds = new Rect[] {new Rect(), new Rect(), new Rect(), new Rect()}; + @VisibleForTesting + Handler mHandler = new Handler(); + @VisibleForTesting + final Runnable mUpdateClickableStateRun = () -> updateClickableState(); + private int[] mLevels; private Paint mTextPaint; private Paint mDividerPaint; private Paint mTrapezoidPaint; - private Paint mTrapezoidCurvePaint = null; + @VisibleForTesting + Paint mTrapezoidCurvePaint = null; private TrapezoidSlot[] mTrapezoidSlots; // Records the location to calculate selected index. private MotionEvent mTouchUpEvent; @@ -257,6 +266,26 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick public void onAttachedToWindow() { super.onAttachedToWindow(); updateClickableState(); + mContext.getSystemService(AccessibilityManager.class) + .addAccessibilityStateChangeListener(/*listener=*/ this); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContext.getSystemService(AccessibilityManager.class) + .removeAccessibilityStateChangeListener(/*listener=*/ this); + mHandler.removeCallbacks(mUpdateClickableStateRun); + } + + @Override + public void onAccessibilityStateChanged(boolean enabled) { + Log.d(TAG, "onAccessibilityStateChanged:" + enabled); + mHandler.removeCallbacks(mUpdateClickableStateRun); + // We should delay it a while since accessibility manager will spend + // some times to bind with new enabled accessibility services. + mHandler.postDelayed( + mUpdateClickableStateRun, UPDATE_STATE_DELAYED_TIME); } private void updateClickableState() { @@ -275,6 +304,10 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick mTrapezoidCurvePaint.setColor(mTrapezoidSolidColor); mTrapezoidCurvePaint.setStyle(Paint.Style.STROKE); mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2); + } else if (mIsSlotsClickabled) { + mTrapezoidCurvePaint = null; + // Sets levels again to force update the click state. + setLevels(mLevels); } invalidate(); } @@ -299,7 +332,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick mDividerHeight = resources.getDimensionPixelSize(R.dimen.chartview_divider_height); mDividerPaint = new Paint(); mDividerPaint.setAntiAlias(true); - mDividerPaint.setColor(mDividerColor); + mDividerPaint.setColor(DIVIDER_COLOR); mDividerPaint.setStyle(Paint.Style.STROKE); mDividerPaint.setStrokeWidth(mDividerWidth); Log.i(TAG, "mDividerWidth:" + mDividerWidth); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java index 877ebc28734..3998a332630 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java @@ -130,6 +130,7 @@ public final class BatteryChartViewTest { mBatteryChartView.onAttachedToWindow(); assertThat(mBatteryChartView.isClickable()).isFalse(); + assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull(); } @Test @@ -141,6 +142,7 @@ public final class BatteryChartViewTest { mBatteryChartView.onAttachedToWindow(); assertThat(mBatteryChartView.isClickable()).isTrue(); + assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull(); } @Test @@ -155,6 +157,7 @@ public final class BatteryChartViewTest { mBatteryChartView.onAttachedToWindow(); assertThat(mBatteryChartView.isClickable()).isTrue(); + assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull(); } @Test @@ -166,5 +169,62 @@ public final class BatteryChartViewTest { mBatteryChartView.onAttachedToWindow(); assertThat(mBatteryChartView.isClickable()).isFalse(); + assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull(); + } + + @Test + public void testClickable_restoreFromNonClickableState() { + final int[] levels = new int[13]; + for (int index = 0; index < levels.length; index++) { + levels[index] = index + 1; + } + mBatteryChartView.setTrapezoidCount(12); + mBatteryChartView.setLevels(levels); + mBatteryChartView.setClickableForce(true); + when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) + .thenReturn(true); + doReturn(true).when(mockAccessibilityManager).isEnabled(); + mBatteryChartView.onAttachedToWindow(); + // Ensures the testing environment is correct. + assertThat(mBatteryChartView.isClickable()).isFalse(); + // Turns off accessibility service. + doReturn(false).when(mockAccessibilityManager).isEnabled(); + + mBatteryChartView.onAttachedToWindow(); + + assertThat(mBatteryChartView.isClickable()).isTrue(); + } + + @Test + public void testOnAttachedToWindow_addAccessibilityStateChangeListener() { + mBatteryChartView.onAttachedToWindow(); + verify(mockAccessibilityManager) + .addAccessibilityStateChangeListener(mBatteryChartView); + } + + @Test + public void testOnDetachedFromWindow_removeAccessibilityStateChangeListener() { + mBatteryChartView.onAttachedToWindow(); + mBatteryChartView.mHandler.postDelayed( + mBatteryChartView.mUpdateClickableStateRun, 1000); + + mBatteryChartView.onDetachedFromWindow(); + + verify(mockAccessibilityManager) + .removeAccessibilityStateChangeListener(mBatteryChartView); + assertThat(mBatteryChartView.mHandler.hasCallbacks( + mBatteryChartView.mUpdateClickableStateRun)) + .isFalse(); + } + + @Test + public void testOnAccessibilityStateChanged_postUpdateStateRunnable() { + mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler); + mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true); + + verify(mBatteryChartView.mHandler) + .removeCallbacks(mBatteryChartView.mUpdateClickableStateRun); + verify(mBatteryChartView.mHandler) + .postDelayed(mBatteryChartView.mUpdateClickableStateRun, 500L); } } From 9a4f0d2c6cb2de74f8ea50d7ba73567600f7b566 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Thu, 13 May 2021 11:04:08 -0700 Subject: [PATCH 07/26] Update a few more strings 1) Fingerprint intro page footer buttons 2) Screen lock removal positive button 3) Update UDFPS string logic 4) Update UDFPS strings during enrollment 5) Update generic fingerprint enroll completion message Bug: 187868985 Test: manually, in settings and SUW Change-Id: Iddcc0a65c3f410b4ae33a760dfcc57aba4bcc7f9 --- res/values/strings.xml | 14 +++++++------- .../fingerprint/FingerprintEnrollEnrolling.java | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index a48ff772249..cb53354aede 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -914,11 +914,11 @@ No thanks - Skip + No thanks Continue - Agree + I agree Skip @@ -1027,21 +1027,21 @@ Put your finger on the sensor and lift after you feel a vibration - Each time you touch, keep your finger on the icon until you feel a vibration + Keep your finger on the icon until you feel a vibration Lift, then touch again One more time - Touch the fingerprint icon as it moves + Follow the fingerprint icon Keep lifting your finger to add the different parts of your fingerprint - This helps capture your full fingerprint + Touch & hold each time the icon moves. This helps capture your full fingerprint. Fingerprint added - When you see this icon, use your fingerprint for identification or to approve purchases + Now you can use your fingerprint to unlock your phone or for authentication, like when you sign in to apps Do it later @@ -1501,7 +1501,7 @@ "Device protection features will not work without your screen lock.\n\nThis deletes the fingerprint model stored on your device. Your face model will also be permanently and securely deleted. You won\u2019t be able to use your face or fingerprint for authentication in apps." - Yes, remove + Delete Change unlock pattern diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 1ad34ba7cfa..6bdd3f7ddd1 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -52,6 +52,7 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupcompat.util.WizardManagerHelper; import java.util.List; @@ -109,6 +110,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { @Nullable private AnimatedVectorDrawable mIconBackgroundBlinksDrawable; private boolean mRestoring; private Vibrator mVibrator; + private boolean mIsSetupWizard; @Override protected void onCreate(Bundle savedInstanceState) { @@ -131,7 +133,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message); } - setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title); + mIsSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); + if (mCanAssumeUdfps && !mIsSetupWizard) { + setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title); + } else { + setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title); + } mErrorText = findViewById(R.id.error_text); mProgressBar = findViewById(R.id.fingerprint_progress_bar); @@ -280,7 +287,11 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message); } } else if (mCanAssumeUdfps && !isCenterEnrollmentComplete()) { - setHeaderText(R.string.security_settings_udfps_enroll_title_one_more_time); + if (mIsSetupWizard) { + setHeaderText(R.string.security_settings_udfps_enroll_title_one_more_time); + } else { + setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title); + } setDescriptionText(R.string.security_settings_udfps_enroll_start_message); } else { if (mCanAssumeUdfps) { From 125ba18f8ea5e15b2730f6b04d55cf35cee693da Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Fri, 14 May 2021 02:48:56 +0800 Subject: [PATCH 08/26] Remove the divider above footer in Gesture Navigation Settings Fixes: 180568503 Test: manual Change-Id: I3cadc106fa0b41a1b98d083d6a2ada5640a4295c --- res/xml/gesture_navigation_settings.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/xml/gesture_navigation_settings.xml b/res/xml/gesture_navigation_settings.xml index b50954222ed..0515d78649f 100644 --- a/res/xml/gesture_navigation_settings.xml +++ b/res/xml/gesture_navigation_settings.xml @@ -49,7 +49,6 @@ android:key="gesture_navigation_settings_footer" android:title="@string/back_sensitivity_dialog_message" android:selectable="false" - settings:searchable="false" - settings:allowDividerAbove="true"/> + settings:searchable="false"/> From 6a43df985517c4698fb000d39fa67eb3d440d0af Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Thu, 13 May 2021 12:30:48 -0700 Subject: [PATCH 09/26] Update UDFPS "find sensor" string for a11y Bug: 187999176 Test: manual Change-Id: I9dea35a70aba87f5f2cc94fc73e74a5a624019b9 --- res/values/strings.xml | 4 +++- .../biometrics/fingerprint/FingerprintEnrollFindSensor.java | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index cb53354aede..b3dd343ab1d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1013,7 +1013,9 @@ It\u2019s on the back of your phone. Use your index finger. - The fingerprint sensor is on your screen. Move your finger across the screen to find it. + The fingerprint sensor is on your screen + + The fingerprint sensor is on your screen. Move your finger across the screen to find the sensor. Illustration with device and fingerprint sensor location diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index db03a3d1efc..c7500e8e300 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -72,6 +72,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements if (mCanAssumeUdfps) { setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title); setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message); + final CharSequence description = getString(R.string.security_settings_udfps_enroll_find_sensor_a11y); + getLayout().getDescriptionTextView().setContentDescription(description); } else { setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message); From 44593a892293358919005b61866867b3112020d2 Mon Sep 17 00:00:00 2001 From: Abel Tesfaye Date: Tue, 11 May 2021 20:14:45 +0000 Subject: [PATCH 10/26] Fix issue where smart auto rotate toggle is not disabled Bug: 187755019 Test: locally on flame, make RunSettingsRoboTests -j96 ROBOTEST_FILTER=SmartAutoRotateControllerTest Change-Id: I44bbd644515d153c834533fe47b3b2c34cfeaac6 --- .../display/SmartAutoRotateController.java | 38 +++++++++++++++---- .../SmartAutoRotatePreferenceFragment.java | 23 ++++------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/com/android/settings/display/SmartAutoRotateController.java b/src/com/android/settings/display/SmartAutoRotateController.java index 90b0d55efc8..61bbd185979 100644 --- a/src/com/android/settings/display/SmartAutoRotateController.java +++ b/src/com/android/settings/display/SmartAutoRotateController.java @@ -18,6 +18,9 @@ package com.android.settings.display; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.provider.Settings.Secure.CAMERA_AUTOROTATE; +import static androidx.lifecycle.Lifecycle.Event.ON_START; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + import android.Manifest; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; @@ -32,7 +35,9 @@ import android.provider.Settings; import android.service.rotationresolver.RotationResolverService; import android.text.TextUtils; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -41,14 +46,12 @@ import com.android.internal.view.RotationPolicy; import com.android.settings.core.TogglePreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; /** * SmartAutoRotateController controls whether auto rotation is enabled */ public class SmartAutoRotateController extends TogglePreferenceController implements - Preference.OnPreferenceChangeListener, LifecycleObserver, OnStart, OnStop { + Preference.OnPreferenceChangeListener, LifecycleObserver { private final MetricsFeatureProvider mMetricsFeatureProvider; private final SensorPrivacyManager mPrivacyManager; @@ -60,6 +63,7 @@ public class SmartAutoRotateController extends TogglePreferenceController implem } }; private Preference mPreference; + private RotationPolicy.RotationPolicyListener mRotationPolicyListener; public SmartAutoRotateController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -70,6 +74,10 @@ public class SmartAutoRotateController extends TogglePreferenceController implem mPowerManager = context.getSystemService(PowerManager.class); } + public void init(Lifecycle lifecycle) { + lifecycle.addObserver(this); + } + @Override public int getAvailabilityStatus() { if (!isRotationResolverServiceAvailable(mContext)) { @@ -101,22 +109,36 @@ public class SmartAutoRotateController extends TogglePreferenceController implem return mPowerManager.isPowerSaveMode(); } - @Override + @OnLifecycleEvent(ON_START) public void onStart() { mContext.registerReceiver(mReceiver, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); + if (mRotationPolicyListener == null) { + mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() { + @Override + public void onChange() { + updateState(mPreference); + } + }; + } + RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener); } - @Override + @OnLifecycleEvent(ON_STOP) public void onStop() { mContext.unregisterReceiver(mReceiver); + if (mRotationPolicyListener != null) { + RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener); + mRotationPolicyListener = null; + } } @Override public boolean isChecked() { - return hasSufficientPermission(mContext) && !isCameraLocked() && !isPowerSaveMode() - && Settings.Secure.getInt(mContext.getContentResolver(), - CAMERA_AUTOROTATE, 0) == 1; + return !RotationPolicy.isRotationLocked(mContext) && hasSufficientPermission(mContext) + && !isCameraLocked() && !isPowerSaveMode() && Settings.Secure.getInt( + mContext.getContentResolver(), + CAMERA_AUTOROTATE, 0) == 1; } @Override diff --git a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java index f697a8c7c48..d56b21e1c76 100644 --- a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java +++ b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java @@ -15,13 +15,11 @@ */ package com.android.settings.display; -import static com.android.settings.display.SmartAutoRotateController.hasSufficientPermission; import static com.android.settings.display.SmartAutoRotateController.isRotationResolverServiceAvailable; import android.app.settings.SettingsEnums; -import android.hardware.SensorPrivacyManager; +import android.content.Context; import android.os.Bundle; -import android.os.PowerManager; import android.text.Html; import android.view.LayoutInflater; import android.view.View; @@ -49,16 +47,19 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { private static final String TAG = "SmartAutoRotatePreferenceFragment"; private RotationPolicy.RotationPolicyListener mRotationPolicyListener; - private SensorPrivacyManager mPrivacyManager; private AutoRotateSwitchBarController mSwitchBarController; - private PowerManager mPowerManager; - private static final String FACE_SWITCH_PREFERENCE_ID = "face_based_rotate"; @Override protected int getPreferenceScreenResId() { return R.xml.auto_rotate_settings; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(SmartAutoRotateController.class).init(getLifecycle()); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -70,8 +71,6 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { switchBar.show(); mSwitchBarController = new AutoRotateSwitchBarController(activity, switchBar, getSettingsLifecycle()); - mPrivacyManager = SensorPrivacyManager.getInstance(activity); - mPowerManager = getSystemService(PowerManager.class); final Preference footerPreference = findPreference(FooterPreference.KEY_FOOTER); if (footerPreference != null) { footerPreference.setTitle(Html.fromHtml(getString(R.string.smart_rotate_text_headline), @@ -89,14 +88,6 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { @Override public void onChange() { mSwitchBarController.onChange(); - final boolean isLocked = RotationPolicy.isRotationLocked(getContext()); - final boolean isCameraLocked = mPrivacyManager.isSensorPrivacyEnabled( - SensorPrivacyManager.Sensors.CAMERA); - final boolean isBatterySaver = mPowerManager.isPowerSaveMode(); - final Preference preference = findPreference(FACE_SWITCH_PREFERENCE_ID); - if (preference != null && hasSufficientPermission(getContext())) { - preference.setEnabled(!isLocked && !isCameraLocked && !isBatterySaver); - } } }; } From dbdaa8518ae84eaea36be69323a5f95d0bd0e7b5 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Thu, 13 May 2021 16:38:01 -0700 Subject: [PATCH 11/26] Fingerprint image is not important for a11y announcements Fixes: 187338706 Test: manual Change-Id: Ic0eac21d0265b5fceb0ab71e013932d3629645b3 --- res/layout/fingerprint_enroll_finish_base.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/layout/fingerprint_enroll_finish_base.xml b/res/layout/fingerprint_enroll_finish_base.xml index 262374d238e..48831dc36c2 100644 --- a/res/layout/fingerprint_enroll_finish_base.xml +++ b/res/layout/fingerprint_enroll_finish_base.xml @@ -52,6 +52,7 @@ android:layout_height="match_parent" android:padding="7dp" android:contentDescription="@android:string/fingerprint_icon_content_description" + android:importantForAccessibility="no" android:src="@drawable/fingerprint_enroll_finish" /> From 1d4c5be0e90e5ea93cf7004c2c0183f660065eb3 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Thu, 13 May 2021 16:53:20 +0800 Subject: [PATCH 12/26] Fixed extra padding in "Open by default" page - Page layout redesign. Bug: 184228266 Test: manual test Change-Id: I1323ca14b1843a95ba1210674595ee3ba1a94c67 --- res/xml/installed_app_launch_settings.xml | 4 ---- .../settings/applications/intentpicker/AppLaunchSettings.java | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/res/xml/installed_app_launch_settings.xml b/res/xml/installed_app_launch_settings.xml index d631e56eaa0..7d971b07d2b 100644 --- a/res/xml/installed_app_launch_settings.xml +++ b/res/xml/installed_app_launch_settings.xml @@ -23,10 +23,6 @@ android:key="open_by_default_supported_links" android:title="@string/app_launch_open_domain_urls_title"/> - - Date: Fri, 14 May 2021 10:35:24 +0800 Subject: [PATCH 13/26] Fix work profile picker user icon didn't follow UX guideline - Remove duplicated dimen in Settings Fixes: 186609520 Test: manual & robotest Change-Id: I256e4f75fe131449b2593d1a8cb422f122a943ac --- res/values/dimens.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 619728201bf..a9cda9e75ac 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -185,9 +185,6 @@ 32 - - 56dp - 58dip 16dip From 6663bed71c6bff345e633cf465f3773c6cf9812c Mon Sep 17 00:00:00 2001 From: Ahaan Ugale Date: Mon, 10 May 2021 11:22:53 -0700 Subject: [PATCH 14/26] Account settings: Fix duplicate title and extra padding. This change reverts the change in I3981ce73fef63f06a40e61e894481d24284614ba to add a PreferenceCategory for the accounts after merging the page with autofill settings. That was done to get the Accounts heading to display for the personal/work profile view. This change instead sets the title on the existing PreferenceCategory that's created dynamically in the Controller. Removing the extra PreferenceCategory also fixes the padding. Fix: 183677515 Bug: 183677706 Test: manual - with single profile, with personal/work profiles Test: make -j RunSettingsRoboTests \ ROBOTEST_FILTER="com.android.settings.accounts.AccountPreferenceControllerTest" Change-Id: Ib7fe6f97b93b81d5a5897280cdd6bbad1b75f3a9 --- res/xml/accounts_dashboard_settings.xml | 44 ++++++++----------- .../accounts_personal_dashboard_settings.xml | 32 ++++++-------- res/xml/accounts_work_dashboard_settings.xml | 34 ++++++-------- .../accounts/AccountPreferenceController.java | 8 ++-- .../AccountPreferenceControllerTest.java | 10 ----- 5 files changed, 49 insertions(+), 79 deletions(-) diff --git a/res/xml/accounts_dashboard_settings.xml b/res/xml/accounts_dashboard_settings.xml index c50eca4761c..c8627e7e9e7 100644 --- a/res/xml/accounts_dashboard_settings.xml +++ b/res/xml/accounts_dashboard_settings.xml @@ -46,33 +46,27 @@ + android:key="dashboard_tile_placeholder" + android:order="130"/> - + - + - - - - + diff --git a/res/xml/accounts_personal_dashboard_settings.xml b/res/xml/accounts_personal_dashboard_settings.xml index 3d88cf9ec12..e0ba71bbef3 100644 --- a/res/xml/accounts_personal_dashboard_settings.xml +++ b/res/xml/accounts_personal_dashboard_settings.xml @@ -47,26 +47,20 @@ + android:key="dashboard_tile_placeholder" + android:order="130"/> - + - - - - + diff --git a/res/xml/accounts_work_dashboard_settings.xml b/res/xml/accounts_work_dashboard_settings.xml index 29e71e28258..1c4c6aa3f3d 100644 --- a/res/xml/accounts_work_dashboard_settings.xml +++ b/res/xml/accounts_work_dashboard_settings.xml @@ -33,7 +33,7 @@ + android:title="@string/autofill_app"> + android:key="dashboard_tile_placeholder" + android:order="130"/> - + - - - - + diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java index 64c2d13cbbc..9776eb62163 100644 --- a/src/com/android/settings/accounts/AccountPreferenceController.java +++ b/src/com/android/settings/accounts/AccountPreferenceController.java @@ -80,7 +80,6 @@ public class AccountPreferenceController extends AbstractPreferenceController private static final int ORDER_NEXT_TO_LAST = 1001; private static final int ORDER_NEXT_TO_NEXT_TO_LAST = 1000; - private static final String PREF_KEY_ACCOUNTS = "accounts_category"; private static final String PREF_KEY_ADD_ACCOUNT = "add_account"; private static final String PREF_KEY_REMOVE_PROFILE = "remove_profile"; private static final String PREF_KEY_WORK_PROFILE_SETTING = "work_profile_setting"; @@ -324,6 +323,7 @@ public class AccountPreferenceController extends AbstractPreferenceController mHelper.createAccessiblePreferenceCategory( mFragment.getPreferenceManager().getContext()); preferenceGroup.setOrder(mAccountProfileOrder++); + preferenceGroup.setTitle(R.string.account_settings); // default title; may be modified below if (isSingleProfile()) { preferenceGroup.setTitle(context.getString(R.string.account_for_section_header, BidiFormatter.getInstance().unicodeWrap(userInfo.name))); @@ -349,10 +349,8 @@ public class AccountPreferenceController extends AbstractPreferenceController } } final PreferenceScreen screen = mFragment.getPreferenceScreen(); - final PreferenceGroup accounts = - screen == null ? null : screen.findPreference(PREF_KEY_ACCOUNTS); - if (accounts != null) { - accounts.addPreference(preferenceGroup); + if (screen != null) { + screen.addPreference(preferenceGroup); } profileData.preferenceGroup = preferenceGroup; if (userInfo.isEnabled()) { diff --git a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java index 4b6a6a8d702..b22b1562be2 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountPreferenceControllerTest.java @@ -39,7 +39,6 @@ import android.os.UserManager; import android.text.TextUtils; import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; @@ -73,13 +72,9 @@ import java.util.List; ShadowSettingsLibUtils.class}) public class AccountPreferenceControllerTest { - private static final String PREF_KEY_ACCOUNTS = "accounts_category"; - @Mock(answer = RETURNS_DEEP_STUBS) private PreferenceScreen mScreen; @Mock(answer = RETURNS_DEEP_STUBS) - private PreferenceCategory mAccountsCategory; - @Mock(answer = RETURNS_DEEP_STUBS) private UserManager mUserManager; @Mock(answer = RETURNS_DEEP_STUBS) private SettingsPreferenceFragment mFragment; @@ -100,9 +95,6 @@ public class AccountPreferenceControllerTest { shadowApp.setSystemService(Context.ACCOUNT_SERVICE, mAccountManager); when(mFragment.getPreferenceScreen()).thenReturn(mScreen); - // This is a bit ugly, but hard to avoid because of how the mocks are used in these tests. - // TODO: Refactor these tests to not use RETURNS_DEEP_STUBS. - when(mScreen.findPreference(PREF_KEY_ACCOUNTS)).thenReturn(mScreen); when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext); when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())) .thenReturn(new AuthenticatorDescription[0]); @@ -174,7 +166,6 @@ public class AccountPreferenceControllerTest { // First time resume will build the UI mController.onResume(); reset(mScreen); - when(mScreen.findPreference(PREF_KEY_ACCOUNTS)).thenReturn(mScreen); mController.onResume(); verify(mScreen, never()).addPreference(any(PreferenceGroup.class)); @@ -193,7 +184,6 @@ public class AccountPreferenceControllerTest { // add a new profile infos.add(new UserInfo(2, "user 2", UserInfo.FLAG_MANAGED_PROFILE)); reset(mScreen); - when(mScreen.findPreference(PREF_KEY_ACCOUNTS)).thenReturn(mScreen); mController.onResume(); verify(mScreen, times(1)).addPreference(any(PreferenceGroup.class)); From 6a78464406528194ffad00f45aee606298ede817 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Fri, 14 May 2021 15:08:00 +0800 Subject: [PATCH 15/26] Replace clearAll with withoutDefaultCapabilities Replace the API as the feedback from API review. Bug: 184735772 Test: make RunSettingsRoboTests \ ROBOTEST_FILTER=WifiDetailPreferenceController2Test Test: make RunSettingsRoboTests \ ROBOTEST_FILTER=ContextualWifiSliceTest Change-Id: I18f0776b6522489bf7e88f984bc44f23d8c9edc4 --- .../wifi/details2/WifiDetailPreferenceController2Test.java | 3 +-- .../android/settings/wifi/slice/ContextualWifiSliceTest.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java index 70ae26b579b..7354d85bc7a 100644 --- a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java +++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java @@ -1048,8 +1048,7 @@ public class WifiDetailPreferenceController2Test { } private NetworkCapabilities makeNetworkCapabilities() { - final NetworkCapabilities nc = new NetworkCapabilities.Builder() - .clearAll() + final NetworkCapabilities nc = NetworkCapabilities.Builder.withoutDefaultCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .build(); return nc; diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java index dc520ab9c3f..32bf5090ddf 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java @@ -144,8 +144,7 @@ public class ContextualWifiSliceTest { } private NetworkCapabilities makeValidatedNetworkCapabilities() { - final NetworkCapabilities nc = new NetworkCapabilities.Builder() - .clearAll() + final NetworkCapabilities nc = NetworkCapabilities.Builder.withoutDefaultCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) .build(); From 637d34ed71fa2ffa4b65752beac9ea81389d5a50 Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 14 May 2021 15:17:37 +0800 Subject: [PATCH 16/26] Wrap generating battery history keys into another method Bug: 188123855 Test: make SettingsRoboTests Change-Id: I82a48e66ec4f7b4c0d9a6756799aa8d7a3a96848 --- .../BatteryChartPreferenceController.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index 1b5c7795022..c435f8f5415 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java @@ -236,16 +236,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll mBatteryHistoryLevels = null; return; } - // Generates battery history timestamp slots. - final List batteryHistoryKeyList = - new ArrayList<>(batteryHistoryMap.keySet()); - Collections.sort(batteryHistoryKeyList); - mBatteryHistoryKeys = new long[CHART_KEY_ARRAY_SIZE]; - for (int index = 0; index < CHART_KEY_ARRAY_SIZE; index++) { - mBatteryHistoryKeys[index] = batteryHistoryKeyList.get(index); - } - - // Generates the battery history levels for chart graph. + mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap); mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE]; for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) { final long timestamp = mBatteryHistoryKeys[index * 2]; @@ -273,7 +264,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll Log.d(TAG, String.format( "setBatteryHistoryMap() size=%d\nkeys=%s\nlevels=%s", - batteryHistoryKeyList.size(), + batteryHistoryMap.size(), utcToLocalTime(mBatteryHistoryKeys), Arrays.toString(mBatteryHistoryLevels))); } @@ -599,4 +590,16 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } return true; } + + private static long[] getBatteryHistoryKeys( + final Map> batteryHistoryMap) { + final List batteryHistoryKeyList = + new ArrayList<>(batteryHistoryMap.keySet()); + Collections.sort(batteryHistoryKeyList); + final long[] batteryHistoryKeys = new long[CHART_KEY_ARRAY_SIZE]; + for (int index = 0; index < CHART_KEY_ARRAY_SIZE; index++) { + batteryHistoryKeys[index] = batteryHistoryKeyList.get(index); + } + return batteryHistoryKeys; + } } From f2c664abd97952d28a1890beeff8ffb6a8d7c290 Mon Sep 17 00:00:00 2001 From: Stanley Wang Date: Thu, 13 May 2021 18:09:00 +0800 Subject: [PATCH 17/26] Guard the NPE on StartNow slice Check if preference is null to avoid NPE when the StartNowPreferenceController is called by slice. Fixes: 187806890 Test: robotest Change-Id: I84c1ee42bc1fd87dd720061e3af81e926028181d --- .../settings/dream/StartNowPreferenceController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/dream/StartNowPreferenceController.java b/src/com/android/settings/dream/StartNowPreferenceController.java index ed0a4dbeece..f6a79ccea01 100644 --- a/src/com/android/settings/dream/StartNowPreferenceController.java +++ b/src/com/android/settings/dream/StartNowPreferenceController.java @@ -59,8 +59,10 @@ public class StartNowPreferenceController extends SettingsMainSwitchPreferenceCo @Override public boolean setChecked(boolean isChecked) { if (isChecked) { - mMetricsFeatureProvider.logClickedPreference(mSwitchPreference, - mSwitchPreference.getExtras().getInt(DashboardFragment.CATEGORY)); + if (mSwitchPreference != null) { + mMetricsFeatureProvider.logClickedPreference(mSwitchPreference, + mSwitchPreference.getExtras().getInt(DashboardFragment.CATEGORY)); + } mBackend.startDreaming(); } return true; From 3e9c576bb8334f4bc1b25569eb913b7cf64045e2 Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Fri, 14 May 2021 16:11:03 +0800 Subject: [PATCH 18/26] Apply transition on the back arrow on NotificationHistory page When clicking on the back arrow from the action bar in the Notification History page, the shared x-axis transition won't work. The onOptionsItemSelected() overriden in the parent activity was removed, so onNavigateUp() will get called. In the NotificationHistoryActivity, onNavigateUp() has been overriden, and so it will just call finish without any transition applied. Hence, remove the override and just use the one define in CollapsingToolbarBaseActivity where finishAfterTransition() is called. Test: rebuild and click on the back arrow to see the transition Fixes: 188126675 Change-Id: I34ff38ffcfb37d0471d325f2c31b726809fbd91f --- .../notification/history/NotificationHistoryActivity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/com/android/settings/notification/history/NotificationHistoryActivity.java b/src/com/android/settings/notification/history/NotificationHistoryActivity.java index bbe36d13b6e..2c806688d1c 100644 --- a/src/com/android/settings/notification/history/NotificationHistoryActivity.java +++ b/src/com/android/settings/notification/history/NotificationHistoryActivity.java @@ -306,12 +306,6 @@ public class NotificationHistoryActivity extends CollapsingToolbarBaseActivity { super.onDestroy(); } - @Override - public boolean onNavigateUp() { - finish(); - return true; - } - private @ColorInt int obtainThemeColor(@AttrRes int attrRes) { Resources.Theme theme = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault_DayNight).getTheme(); From c7dbd26fbfb140ae81417e2bd0b71a5d244e5a01 Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Fri, 14 May 2021 16:23:35 +0800 Subject: [PATCH 19/26] Fix test case failed Fix ConnectedDeviceDashboardFragmentTest test case failed. Bug: 187999771 Test: make RunSettingsRoboTests -j56 Change-Id: I7c2617d1305c3e648f14ba8982d716ef1034e66e --- .../connecteddevice/ConnectedDeviceDashboardFragmentTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index c4f15874503..ea6947e2dee 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -53,6 +53,7 @@ public class ConnectedDeviceDashboardFragmentTest { private static final String KEY_NEARBY_DEVICES = "bt_nearby_slice"; private static final String KEY_DISCOVERABLE_FOOTER = "discoverable_footer"; private static final String KEY_SEE_ALL = "previously_connected_devices_see_all"; + private static final String KEY_ADD_BT_DEVICES = "add_bt_devices"; @Mock private PackageManager mPackageManager; @@ -83,7 +84,7 @@ public class ConnectedDeviceDashboardFragmentTest { .getNonIndexableKeys(mContext); assertThat(niks).containsExactly(KEY_CONNECTED_DEVICES, KEY_AVAILABLE_DEVICES, - KEY_NEARBY_DEVICES, KEY_DISCOVERABLE_FOOTER, KEY_SEE_ALL); + KEY_NEARBY_DEVICES, KEY_DISCOVERABLE_FOOTER, KEY_SEE_ALL, KEY_ADD_BT_DEVICES); } @Test From b61388d1f5be1281ea43f05b57c09874d9976519 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Fri, 14 May 2021 10:38:08 +0800 Subject: [PATCH 20/26] Fix hint text color contrast problem in LocalePickerWithRegionActivity Set android:textColorHint textColorSecondary in Theme.Settings. Bug: 147637981 Test: accessibility scanner Change-Id: Ie3ef7e86909b75366c427ac5c6a90a9a02f0f660 --- res/values/styles.xml | 1 - res/values/themes.xml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/styles.xml b/res/values/styles.xml index b7a6dabc3ae..d98e25b5945 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -145,7 +145,6 @@ 4dip viewStart @android:style/TextAppearance.DeviceDefault.Medium - ?android:attr/textColorSecondary @dimen/min_tap_target_size diff --git a/res/values/themes.xml b/res/values/themes.xml index af44276c281..555a0454a66 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -32,6 +32,7 @@ @android:color/transparent @style/PickerDialogTheme.Settings @style/HorizontalProgressBarStyle + ?android:attr/textColorSecondary @style/FingerprintLayoutTheme @style/FaceLayoutTheme From e8d1374ea21179d671a2e45df0990a51c702de77 Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 14 May 2021 15:37:29 +0800 Subject: [PATCH 21/26] Export a method for app info page to get last 24 battery history data Bug: 188123855 Test: make SettingsRoboTests Change-Id: I1953461c47a33c5951b94726e6eade21033a1403 --- .../BatteryChartPreferenceController.java | 22 +++++++++++++++ .../settings/fuelgauge/ConvertUtilsTest.java | 28 ++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index c435f8f5415..a8032256bd6 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java @@ -35,6 +35,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -591,6 +592,27 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return true; } + static List getBatteryLast24HrUsageData(Context context) { + final long start = System.currentTimeMillis(); + final Map> batteryHistoryMap = + FeatureFactory.getFactory(context) + .getPowerUsageFeatureProvider(context) + .getBatteryHistory(context); + if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { + return null; + } + Log.d(TAG, String.format("getBatteryLast24HrData() size=%d time=&d/ms", + batteryHistoryMap.size(), (System.currentTimeMillis() - start))); + final Map> batteryIndexedMap = + ConvertUtils.getIndexedUsageMap( + context, + /*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1, + getBatteryHistoryKeys(batteryHistoryMap), + batteryHistoryMap, + /*purgeLowPercentageAndFakeData=*/ true); + return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL); + } + private static long[] getBatteryHistoryKeys( final Map> batteryHistoryMap) { final List batteryHistoryKeyList = diff --git a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java index 216b118a029..63a0b3d71d1 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java @@ -26,6 +26,8 @@ import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.os.UserHandle; +import com.android.settings.testutils.FakeFeatureFactory; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,15 +47,18 @@ import java.util.TimeZone; public final class ConvertUtilsTest { private Context mContext; - @Mock - private BatteryUsageStats mBatteryUsageStats; - @Mock - private BatteryEntry mockBatteryEntry; + @Mock private BatteryUsageStats mBatteryUsageStats; + @Mock private BatteryEntry mockBatteryEntry; + + private FakeFeatureFactory mFeatureFactory; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider; } @Test @@ -250,6 +255,21 @@ public final class ConvertUtilsTest { // Verifies the fake data is cleared out. assertThat(entryList.get(0).getPackageName()) .isNotEqualTo(ConvertUtils.FAKE_PACKAGE_NAME); + + // Adds lacked data into the battery history map. + final int remainingSize = 25 - batteryHistoryKeys.length; + for (int index = 0; index < remainingSize; index++) { + batteryHistoryMap.put(105L + index + 1, new HashMap<>()); + } + when(mPowerUsageFeatureProvider.getBatteryHistory(mContext)) + .thenReturn(batteryHistoryMap); + + final List batteryDiffEntryList = + BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext); + + assertThat(batteryDiffEntryList).isNotEmpty(); + final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0); + assertThat(resultEntry.getPackageName()).isEqualTo("package2"); } @Test From 3eb374adb4c94db060fb1b31bc08354b293a2725 Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Fri, 14 May 2021 15:49:51 +0800 Subject: [PATCH 22/26] Fix foreground Service background launch restriction Remove setForeground() in BluetoothPairingService. Refactor the behavior to not holding service in the background. Bug: 184901840 Test: make RunSettingsRoboTests -j56 Change-Id: Iaeb96943971f55dbeb3dba4b06b28abe770ba3c2 --- .../bluetooth/BluetoothPairingRequest.java | 63 ++++----- .../bluetooth/BluetoothPairingService.java | 122 +++++++++++------ .../BluetoothPairingServiceTest.java | 125 ++++++++++++++++++ 3 files changed, 236 insertions(+), 74 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingServiceTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java index d7dad887fec..993f584dd36 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java @@ -31,38 +31,39 @@ import android.os.UserHandle; */ public final class BluetoothPairingRequest extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action == null || !action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - return; - } - PowerManager powerManager = context.getSystemService(PowerManager.class); - BluetoothDevice device = - intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - int pairingVariant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, - BluetoothDevice.ERROR); - String deviceAddress = device != null ? device.getAddress() : null; - String deviceName = device != null ? device.getName() : null; - boolean shouldShowDialog = LocalBluetoothPreferences.shouldShowDialogInForeground( - context, deviceAddress, deviceName); + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null || !action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { + return; + } - // Skips consent pairing dialog if the device was recently associated with CDM - if (pairingVariant == BluetoothDevice.PAIRING_VARIANT_CONSENT - && device.canBondWithoutDialog()) { - device.setPairingConfirmation(true); - } else if (powerManager.isInteractive() && shouldShowDialog) { - // Since the screen is on and the BT-related activity is in the foreground, - // just open the dialog - // convert broadcast intent into activity intent (same action string) - Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent, - BluetoothDevice.EXTRA_PAIRING_INITIATOR_FOREGROUND); + PowerManager powerManager = context.getSystemService(PowerManager.class); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + int pairingVariant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.ERROR); + String deviceAddress = device != null ? device.getAddress() : null; + String deviceName = device != null ? device.getName() : null; + boolean shouldShowDialog = LocalBluetoothPreferences.shouldShowDialogInForeground( + context, deviceAddress, deviceName); - context.startActivityAsUser(pairingIntent, UserHandle.CURRENT); - } else { - // Put up a notification that leads to the dialog - intent.setClass(context, BluetoothPairingService.class); - context.startServiceAsUser(intent, UserHandle.CURRENT); + // Skips consent pairing dialog if the device was recently associated with CDM + if (pairingVariant == BluetoothDevice.PAIRING_VARIANT_CONSENT + && device.canBondWithoutDialog()) { + device.setPairingConfirmation(true); + } else if (powerManager.isInteractive() && shouldShowDialog) { + // Since the screen is on and the BT-related activity is in the foreground, + // just open the dialog + // convert broadcast intent into activity intent (same action string) + Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent, + BluetoothDevice.EXTRA_PAIRING_INITIATOR_FOREGROUND); + + context.startActivityAsUser(pairingIntent, UserHandle.CURRENT); + } else { + // Put up a notification that leads to the dialog + intent.setClass(context, BluetoothPairingService.class); + intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST); + context.startServiceAsUser(intent, UserHandle.CURRENT); + } } - } } diff --git a/src/com/android/settings/bluetooth/BluetoothPairingService.java b/src/com/android/settings/bluetooth/BluetoothPairingService.java index c411b0cd0b4..0bff721b8a9 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingService.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingService.java @@ -31,6 +31,9 @@ import android.os.IBinder; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.VisibleForTesting; +import androidx.core.app.NotificationCompat; + import com.android.settings.R; /** @@ -39,10 +42,14 @@ import com.android.settings.R; */ public final class BluetoothPairingService extends Service { - private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; - - private static final String ACTION_DISMISS_PAIRING = + @VisibleForTesting + static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; + @VisibleForTesting + static final String ACTION_DISMISS_PAIRING = "com.android.settings.bluetooth.ACTION_DISMISS_PAIRING"; + @VisibleForTesting + static final String ACTION_PAIRING_DIALOG = + "com.android.settings.bluetooth.ACTION_PAIRING_DIALOG"; private static final String BLUETOOTH_NOTIFICATION_CHANNEL = "bluetooth_notification_channel"; @@ -51,6 +58,9 @@ public final class BluetoothPairingService extends Service { private BluetoothDevice mDevice; + @VisibleForTesting + NotificationManager mNm; + public static Intent getPairingDialogIntent(Context context, Intent intent, int initiator) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, @@ -80,33 +90,35 @@ public final class BluetoothPairingService extends Service { if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); + Log.d(TAG, "onReceive() Bond state change : " + bondState + ", device name : " + + mDevice.getName()); if ((bondState != BluetoothDevice.BOND_NONE) && (bondState != BluetoothDevice.BOND_BONDED)) { return; } } else if (action.equals(ACTION_DISMISS_PAIRING)) { - Log.d(TAG, "Notification cancel " + mDevice.getAddress() + " (" + + Log.d(TAG, "Notification cancel " + " (" + mDevice.getName() + ")"); mDevice.cancelPairing(); } else { int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); - Log.d(TAG, "Dismiss pairing for " + mDevice.getAddress() + " (" + + Log.d(TAG, "Dismiss pairing for " + " (" + mDevice.getName() + "), BondState: " + bondState); } - stopForeground(true); + + mNm.cancel(NOTIFICATION_ID); stopSelf(); } }; @Override public void onCreate() { - NotificationManager mgr = (NotificationManager)this - .getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel notificationChannel = new NotificationChannel( - BLUETOOTH_NOTIFICATION_CHANNEL, - this.getString(R.string.bluetooth), - NotificationManager.IMPORTANCE_HIGH); - mgr.createNotificationChannel(notificationChannel); + mNm = getSystemService(NotificationManager.class); + NotificationChannel notificationChannel = new NotificationChannel( + BLUETOOTH_NOTIFICATION_CHANNEL, + this.getString(R.string.bluetooth), + NotificationManager.IMPORTANCE_HIGH); + mNm.createNotificationChannel(notificationChannel); } @Override @@ -116,23 +128,8 @@ public final class BluetoothPairingService extends Service { stopSelf(); return START_NOT_STICKY; } - - Resources res = getResources(); - Notification.Builder builder = new Notification.Builder(this, - BLUETOOTH_NOTIFICATION_CHANNEL) - .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) - .setTicker(res.getString(R.string.bluetooth_notif_ticker)) - .setLocalOnly(true); - - PendingIntent pairIntent = PendingIntent.getActivity(this, 0, - getPairingDialogIntent(this, intent, - BluetoothDevice.EXTRA_PAIRING_INITIATOR_BACKGROUND), - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_IMMUTABLE); - - PendingIntent dismissIntent = PendingIntent.getBroadcast(this, 0, - new Intent(ACTION_DISMISS_PAIRING), PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE); + String action = intent.getAction(); + Log.d(TAG, "onStartCommand() action : " + action); mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); @@ -142,36 +139,76 @@ public final class BluetoothPairingService extends Service { return START_NOT_STICKY; } + if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) { + createPairingNotification(intent); + } else if (TextUtils.equals(action, ACTION_DISMISS_PAIRING)) { + Log.d(TAG, "Notification cancel " + " (" + mDevice.getName() + ")"); + mDevice.cancelPairing(); + mNm.cancel(NOTIFICATION_ID); + stopSelf(); + } else if (TextUtils.equals(action, ACTION_PAIRING_DIALOG)) { + Intent pairingDialogIntent = getPairingDialogIntent(this, intent, + BluetoothDevice.EXTRA_PAIRING_INITIATOR_BACKGROUND); + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL); + filter.addAction(ACTION_DISMISS_PAIRING); + registerReceiver(mCancelReceiver, filter); + mRegistered = true; + + startActivity(pairingDialogIntent); + } + + return START_STICKY; + } + + private void createPairingNotification(Intent intent) { + Resources res = getResources(); + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, + BLUETOOTH_NOTIFICATION_CHANNEL) + .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) + .setTicker(res.getString(R.string.bluetooth_notif_ticker)) + .setLocalOnly(true); + + int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.ERROR); + Intent pairingDialogIntent = new Intent(ACTION_PAIRING_DIALOG); + pairingDialogIntent.setClass(this, BluetoothPairingService.class); + pairingDialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + pairingDialogIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, type); + PendingIntent pairIntent = PendingIntent.getService(this, 0, pairingDialogIntent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); + + Intent serviceIntent = new Intent(ACTION_DISMISS_PAIRING); + serviceIntent.setClass(this, BluetoothPairingService.class); + serviceIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + PendingIntent dismissIntent = PendingIntent.getService(this, 0, + serviceIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); + String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); if (TextUtils.isEmpty(name)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); name = device != null ? device.getAlias() : res.getString(android.R.string.unknownName); } - Log.d(TAG, "Show pairing notification for " + mDevice.getAddress() + " (" + name + ")"); + Log.d(TAG, "Show pairing notification for " + " (" + name + ")"); - Notification.Action pairAction = new Notification.Action.Builder(0, + NotificationCompat.Action pairAction = new NotificationCompat.Action.Builder(0, res.getString(R.string.bluetooth_device_context_pair_connect), pairIntent).build(); - Notification.Action dismissAction = new Notification.Action.Builder(0, + NotificationCompat.Action dismissAction = new NotificationCompat.Action.Builder(0, res.getString(android.R.string.cancel), dismissIntent).build(); builder.setContentTitle(res.getString(R.string.bluetooth_notif_title)) .setContentText(res.getString(R.string.bluetooth_notif_message, name)) .setContentIntent(pairIntent) .setDefaults(Notification.DEFAULT_SOUND) + .setOngoing(true) .setColor(getColor(com.android.internal.R.color.system_notification_accent_color)) .addAction(pairAction) .addAction(dismissAction); - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL); - filter.addAction(ACTION_DISMISS_PAIRING); - registerReceiver(mCancelReceiver, filter); - mRegistered = true; - - startForeground(NOTIFICATION_ID, builder.getNotification()); - return START_REDELIVER_INTENT; + mNm.notify(NOTIFICATION_ID, builder.build()); } @Override @@ -180,7 +217,6 @@ public final class BluetoothPairingService extends Service { unregisterReceiver(mCancelReceiver); mRegistered = false; } - stopForeground(true); } @Override diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingServiceTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingServiceTest.java new file mode 100644 index 00000000000..e73b447728e --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingServiceTest.java @@ -0,0 +1,125 @@ +/* + * 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.settings.bluetooth.BluetoothPairingService.ACTION_DISMISS_PAIRING; +import static com.android.settings.bluetooth.BluetoothPairingService.ACTION_PAIRING_DIALOG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Application; +import android.app.NotificationManager; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.util.DisplayMetrics; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothPairingServiceTest { + + private final String mFakeTicker = "fake_ticker"; + + @Mock + private NotificationManager mNm; + @Mock + private BluetoothDevice mDevice; + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private PackageManager mPackageManager; + @Mock + private DisplayMetrics mDisplayMetrics; + + private BluetoothPairingService mBluetoothPairingService; + private Application mApplication; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mBluetoothPairingService = new BluetoothPairingService(); + mBluetoothPairingService.mNm = mNm; + mApplication = RuntimeEnvironment.application; + + ReflectionHelpers.setField(mBluetoothPairingService, "mBase", mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getString(R.string.bluetooth_notif_ticker)).thenReturn(mFakeTicker); + when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + mDisplayMetrics.density = 1.5f; + } + + @Test + public void receivePairingRequestAction_notificationShown() { + Intent intent = new Intent(); + intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name"); + when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING); + + mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0); + + verify(mNm).notify(eq(mBluetoothPairingService.NOTIFICATION_ID), any()); + } + + @Test + public void receiveDismissPairingAction_cancelPairing() { + Intent intent = new Intent(); + intent.setAction(ACTION_DISMISS_PAIRING); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name"); + when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING); + + mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0); + + verify(mDevice).cancelPairing(); + verify(mNm).cancel(mBluetoothPairingService.NOTIFICATION_ID); + } + + @Test + public void receivePairingDialogAction_startActivity() { + Intent intent = new Intent(); + intent.setAction(ACTION_PAIRING_DIALOG); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name"); + when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING); + + mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0); + + verify(mContext).startActivity(any()); + } +} From 6cbeb65a05033ad51f9f2ce134b24c15e8695b00 Mon Sep 17 00:00:00 2001 From: Tim Peng Date: Thu, 13 May 2021 13:33:05 +0800 Subject: [PATCH 23/26] [Rohan] Two battery level, but no charging status in Rohan Setting -show/hide battery information by device type Bug: 187692468 Test: atest AdvancedBluetoothDetailsHeaderControllerTest Change-Id: I22b4f2cddaf1143dd48ffec2fa3114d685689633 --- ...ancedBluetoothDetailsHeaderController.java | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java index cc1458626c0..5851b506e4e 100644 --- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java @@ -274,37 +274,43 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont showBatteryPredictionIfNecessary(linearLayout, deviceId, batteryLevel); } final TextView batterySummaryView = linearLayout.findViewById(R.id.bt_battery_summary); - if (batteryLevel != BluetoothUtils.META_INT_ERROR) { - linearLayout.setVisibility(View.VISIBLE); - batterySummaryView.setText(com.android.settings.Utils.formatPercentage(batteryLevel)); - batterySummaryView.setVisibility(View.VISIBLE); - int lowBatteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice, lowBatteryMetaKey); - if (lowBatteryLevel == BluetoothUtils.META_INT_ERROR) { - if (batteryMetaKey == BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY) { - lowBatteryLevel = CASE_LOW_BATTERY_LEVEL; - } else { - lowBatteryLevel = LOW_BATTERY_LEVEL; - } - } - showBatteryIcon(linearLayout, batteryLevel, lowBatteryLevel, charging); - } else { - if (deviceId == MAIN_DEVICE_ID) { + if (isUntetheredHeadset(bluetoothDevice)) { + if (batteryLevel != BluetoothUtils.META_INT_ERROR) { linearLayout.setVisibility(View.VISIBLE); - linearLayout.findViewById(R.id.bt_battery_icon).setVisibility(View.GONE); - int level = bluetoothDevice.getBatteryLevel(); - if (level != BluetoothDevice.BATTERY_LEVEL_UNKNOWN - && level != BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF) { - batterySummaryView.setText(com.android.settings.Utils.formatPercentage(level)); - batterySummaryView.setVisibility(View.VISIBLE); - } else { - batterySummaryView.setVisibility(View.GONE); + batterySummaryView.setText( + com.android.settings.Utils.formatPercentage(batteryLevel)); + batterySummaryView.setVisibility(View.VISIBLE); + int lowBatteryLevel = BluetoothUtils.getIntMetaData(bluetoothDevice, + lowBatteryMetaKey); + if (lowBatteryLevel == BluetoothUtils.META_INT_ERROR) { + if (batteryMetaKey == BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY) { + lowBatteryLevel = CASE_LOW_BATTERY_LEVEL; + } else { + lowBatteryLevel = LOW_BATTERY_LEVEL; + } } + showBatteryIcon(linearLayout, batteryLevel, lowBatteryLevel, charging); } else { - // Hide it if it doesn't have battery information - linearLayout.setVisibility(View.GONE); + if (deviceId == MAIN_DEVICE_ID) { + linearLayout.setVisibility(View.VISIBLE); + linearLayout.findViewById(R.id.bt_battery_icon).setVisibility(View.GONE); + int level = bluetoothDevice.getBatteryLevel(); + if (level != BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && level != BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF) { + batterySummaryView.setText( + com.android.settings.Utils.formatPercentage(level)); + batterySummaryView.setVisibility(View.VISIBLE); + } else { + batterySummaryView.setVisibility(View.GONE); + } + } else { + // Hide it if it doesn't have battery information + linearLayout.setVisibility(View.GONE); + } } + } else { + batterySummaryView.setVisibility(View.GONE); } - final TextView textView = linearLayout.findViewById(R.id.header_title); if (deviceId == MAIN_DEVICE_ID) { textView.setVisibility(View.GONE); @@ -314,6 +320,14 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont } } + private boolean isUntetheredHeadset(BluetoothDevice bluetoothDevice) { + return BluetoothUtils.getBooleanMetaData(bluetoothDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET) + || TextUtils.equals(BluetoothUtils.getStringMetaData(bluetoothDevice, + BluetoothDevice.METADATA_DEVICE_TYPE), + BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET); + } + private void showBatteryPredictionIfNecessary(LinearLayout linearLayout, int batteryId, int batteryLevel) { ThreadUtils.postOnBackgroundThread(() -> { From be4c5f078fa1e1c367e722ccba61271dd0200014 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Fri, 7 May 2021 00:07:26 +0800 Subject: [PATCH 24/26] Remove silky flag from homepage for official release Remove the silky condition and clean up redundant files. Bug: 183670633 Test: robotests & visual with turning on/off silky home Change-Id: I9259108534935ed8551748b922098dd52043afc1 --- res/xml/top_level_settings.xml | 179 ++++++++++-------- res/xml/top_level_settings_grouped.xml | 177 ----------------- .../DashboardFeatureProviderImpl.java | 5 +- .../homepage/SettingsHomepageActivity.java | 7 +- .../settings/homepage/TopLevelSettings.java | 23 +-- .../homepage/TopLevelSettingsTest.java | 15 -- 6 files changed, 101 insertions(+), 305 deletions(-) delete mode 100644 res/xml/top_level_settings_grouped.xml diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index 725390e1404..6d73ad6654f 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -21,140 +21,157 @@ android:key="top_level_settings"> + android:fragment="com.android.settings.applications.AppDashboardFragment" + android:icon="@drawable/ic_apps" + android:key="top_level_apps" + android:order="-130" + android:title="@string/apps_dashboard_title" + android:summary="@string/app_and_notification_dashboard_summary"/> + + - - - - + android:fragment="com.android.settings.notification.SoundSettings" + android:icon="@drawable/ic_volume_up_24dp" + android:key="top_level_sound" + android:order="-90" + android:title="@string/sound_settings" + android:summary="@string/sound_dashboard_summary"/> + android:fragment="com.android.settings.DisplaySettings" + android:icon="@drawable/ic_settings_display_white" + android:key="top_level_display" + android:order="-80" + android:title="@string/display_settings" + android:summary="@string/display_dashboard_summary" + settings:controller="com.android.settings.display.TopLevelDisplayPreferenceController"/> + + + + + android:fragment="com.android.settings.privacy.PrivacyDashboardFragment" + android:icon="@drawable/ic_settings_privacy" + android:key="top_level_privacy" + android:order="-40" + android:title="@string/privacy_dashboard_title" + android:summary="@string/privacy_dashboard_summary"/> + android:fragment="com.android.settings.location.LocationSettings" + android:icon="@drawable/ic_settings_location" + android:key="top_level_location" + android:order="-30" + android:title="@string/location_settings_title" + android:summary="@string/location_settings_loading_app_permission_stats" + settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/> + android:fragment="com.android.settings.accounts.AccountDashboardFragment" + android:icon="@drawable/ic_settings_accounts" + android:key="top_level_accounts" + android:order="-10" + android:title="@string/account_dashboard_title" + android:summary="@string/summary_placeholder" + settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/> + + diff --git a/res/xml/top_level_settings_grouped.xml b/res/xml/top_level_settings_grouped.xml deleted file mode 100644 index 7b4f8a10773..00000000000 --- a/res/xml/top_level_settings_grouped.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index 5a686c26a06..0de86f5b668 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -47,7 +47,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; import android.widget.Toast; @@ -60,7 +59,6 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; -import com.android.settings.core.FeatureFlags; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PrimarySwitchPreference; @@ -399,8 +397,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { private void setPreferenceIcon(Preference preference, Tile tile, boolean forceRoundedIcon, String iconPackage, Icon icon) { Drawable iconDrawable = icon.loadDrawable(preference.getContext()); - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.SILKY_HOME) - && TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) { + if (TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) { iconDrawable.setTint(Utils.getHomepageIconColor(preference.getContext())); } else if (forceRoundedIcon && !TextUtils.equals(mContext.getPackageName(), iconPackage)) { iconDrawable = new AdaptiveIcon(mContext, iconDrawable, diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index c59b805c437..5950e4b8ba1 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -96,12 +96,7 @@ public class SettingsHomepageActivity extends FragmentActivity { getLifecycle().addObserver(new AvatarViewMixin(this, avatarView)); } - if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SILKY_HOME)) { - showSuggestionFragment(); - } else { - findViewById(R.id.homepage_title).setVisibility(View.GONE); - avatarView.setVisibility(View.GONE); - } + showSuggestionFragment(); if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) { showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content); diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index f4f7f61cd1d..681ea5111c7 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -23,8 +23,6 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.provider.SearchIndexableResource; -import android.util.FeatureFlagUtils; import androidx.fragment.app.Fragment; import androidx.preference.Preference; @@ -33,7 +31,6 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.core.FeatureFlags; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; @@ -41,9 +38,6 @@ import com.android.settings.support.SupportPreferenceController; import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.search.SearchIndexable; -import java.util.Arrays; -import java.util.List; - @SearchIndexable(forTarget = MOBILE) public class TopLevelSettings extends DashboardFragment implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { @@ -59,9 +53,6 @@ public class TopLevelSettings extends DashboardFragment implements @Override protected int getPreferenceScreenResId() { - if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.SILKY_HOME)) { - return R.xml.top_level_settings_grouped; - } return R.xml.top_level_settings; } @@ -108,9 +99,6 @@ public class TopLevelSettings extends DashboardFragment implements @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); - if (!FeatureFlagUtils.isEnabled(getContext(), FeatureFlags.SILKY_HOME)) { - return; - } final PreferenceScreen screen = getPreferenceScreen(); if (screen == null) { return; @@ -137,16 +125,7 @@ public class TopLevelSettings extends DashboardFragment implements } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - - @Override - public List getXmlResourcesToIndex( - Context context, boolean enabled) { - final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = FeatureFlagUtils.isEnabled(context, FeatureFlags.SILKY_HOME) - ? R.xml.top_level_settings_grouped : R.xml.top_level_settings; - return Arrays.asList(sir); - } + new BaseSearchIndexProvider(R.xml.top_level_settings) { @Override protected boolean isPageSearchEnabled(Context context) { diff --git a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java index 663d7f72a1a..da2f8b5e566 100644 --- a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java +++ b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java @@ -28,13 +28,11 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.util.FeatureFlagUtils; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.core.FeatureFlags; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; @@ -65,19 +63,6 @@ public class TopLevelSettingsTest { assertThat(mSettings.shouldForceRoundedIcon()).isTrue(); } - @Test - public void getPreferenceScreenResId_silkyHomeDisabled_defaultSettings() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.SILKY_HOME, false); - assertThat(mSettings.getPreferenceScreenResId()).isEqualTo(R.xml.top_level_settings); - } - - @Test - public void getPreferenceScreenResId_silkyHomeEnabled_groupedSettings() { - FeatureFlagUtils.setEnabled(mContext, FeatureFlags.SILKY_HOME, true); - assertThat(mSettings.getPreferenceScreenResId()).isEqualTo( - R.xml.top_level_settings_grouped); - } - @Test public void onCreatePreferences_shouldTintPreferenceIcon() { final Preference preference = new Preference(mContext); From e6070f9f0efff3480f3787b922925d849381e3d9 Mon Sep 17 00:00:00 2001 From: Jan Tomljanovic Date: Thu, 13 May 2021 18:56:40 +0100 Subject: [PATCH 25/26] Filter out SecuritySettings search results when alternative available. When alternative fragment is shown for the security settings page, we don't want to show search results that would lead to an unused security settings page. Test: manual Test: atest SettingsUnitTests Test: make RunSettingsRoboTests -j Bug: 184613149 Change-Id: I525bd87d1ac6a24a7a26f59ae917e35ac39732e3 --- .../settings/security/SecuritySettings.java | 7 ++ .../security/SecuritySettingsTest.java | 119 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 tests/unit/src/com/android/settings/security/SecuritySettingsTest.java diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java index 01cdba7b3ca..8a03a04f34d 100644 --- a/src/com/android/settings/security/SecuritySettings.java +++ b/src/com/android/settings/security/SecuritySettings.java @@ -31,6 +31,7 @@ import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceCo import com.android.settings.dashboard.DashboardFragment; import com.android.settings.enterprise.EnterprisePrivacyPreferenceController; import com.android.settings.enterprise.FinancedPrivacyPreferenceController; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.security.trustagent.ManageTrustAgentsPreferenceController; import com.android.settings.security.trustagent.TrustAgentListPreferenceController; @@ -152,5 +153,11 @@ public class SecuritySettings extends DashboardFragment { return buildPreferenceControllers(context, null /* lifecycle */, null /* host*/); } + + @Override + protected boolean isPageSearchEnabled(Context context) { + return !FeatureFactory.getFactory(context).getSecuritySettingsFeatureProvider() + .hasAlternativeSecuritySettingsFragment(); + } }; } diff --git a/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java b/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java new file mode 100644 index 00000000000..c50d99312ae --- /dev/null +++ b/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java @@ -0,0 +1,119 @@ +/* + * 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.security; + +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; +import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_INCLUDE_PREF_SCREEN; +import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.annotation.XmlRes; +import android.content.Context; +import android.os.Bundle; +import android.provider.SearchIndexableResource; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.core.PreferenceXmlParserUtils; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.security.trustagent.TrustAgentManager; +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + + +@RunWith(AndroidJUnit4.class) +public class SecuritySettingsTest { + + private Context mContext; + private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider; + + @Mock + private TrustAgentManager mTrustAgentManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + FakeFeatureFactory mFeatureFactory = FakeFeatureFactory.setupForTest(); + mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider(); + SecurityFeatureProvider mSecurityFeatureProvider = + mFeatureFactory.getSecurityFeatureProvider(); + + when(mSecurityFeatureProvider.getTrustAgentManager()).thenReturn(mTrustAgentManager); + } + + @Test + public void noAlternativeFragmentAvailable_pageIndexIncluded() throws Exception { + when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()) + .thenReturn(false); + BaseSearchIndexProvider indexProvider = SecuritySettings.SEARCH_INDEX_DATA_PROVIDER; + + List allXmlKeys = getAllXmlKeys(indexProvider); + List nonIndexableKeys = indexProvider.getNonIndexableKeys(mContext); + allXmlKeys.removeAll(nonIndexableKeys); + + assertThat(allXmlKeys).isNotEmpty(); + } + + @Test + public void alternativeFragmentAvailable_pageIndexExcluded() throws Exception { + when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()) + .thenReturn(true); + BaseSearchIndexProvider indexProvider = SecuritySettings.SEARCH_INDEX_DATA_PROVIDER; + + List allXmlKeys = getAllXmlKeys(indexProvider); + List nonIndexableKeys = indexProvider.getNonIndexableKeys(mContext); + allXmlKeys.removeAll(nonIndexableKeys); + + assertThat(allXmlKeys).isEmpty(); + } + + private List getAllXmlKeys(BaseSearchIndexProvider indexProvider) throws Exception { + final List resources = indexProvider.getXmlResourcesToIndex( + mContext, true /* not used*/); + if (resources == null || resources.isEmpty()) { + return new ArrayList<>(); + } + final List keys = new ArrayList<>(); + for (SearchIndexableResource res : resources) { + keys.addAll(getKeysFromXml(res.xmlResId)); + } + return keys; + } + + private List getKeysFromXml(@XmlRes int xmlResId) throws Exception { + final List keys = new ArrayList<>(); + final List metadata = PreferenceXmlParserUtils + .extractMetadata(mContext, xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN); + for (Bundle bundle : metadata) { + keys.add(bundle.getString(METADATA_KEY)); + } + return keys; + } +} From 1ec8f04feb526e920ec8f7c81bafe7eeb6fadb85 Mon Sep 17 00:00:00 2001 From: ykhung Date: Fri, 14 May 2021 20:58:58 +0800 Subject: [PATCH 26/26] Add battery usage metric Bug: 188046672 Test: make SettingsRoboTests Change-Id: I1716913eee7c93cafd622052dcbca940c9726a17 --- .../BatteryChartPreferenceController.java | 28 ++++++++-- .../BatteryChartPreferenceControllerTest.java | 52 ++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java index a8032256bd6..7486f22d0a8 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java @@ -16,6 +16,7 @@ package com.android.settings.fuelgauge; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; @@ -37,6 +38,7 @@ import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreate; @@ -89,9 +91,10 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll private final String mPreferenceKey; private final SettingsActivity mActivity; private final InstrumentedPreferenceFragment mFragment; - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final CharSequence[] mNotAllowShowSummaryPackages; private final CharSequence[] mNotAllowShowEntryPackages; + private final CharSequence[] mNotAllowShowSummaryPackages; + private final MetricsFeatureProvider mMetricsFeatureProvider; + private final Handler mHandler = new Handler(Looper.getMainLooper()); // Preference cache to avoid create new instance each time. @VisibleForTesting @@ -111,6 +114,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll .getTextArray(R.array.allowlist_hide_summary_in_battery_usage); mNotAllowShowEntryPackages = context.getResources() .getTextArray(R.array.allowlist_hide_entry_in_battery_usage); + mMetricsFeatureProvider = + FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(); if (lifecycle != null) { lifecycle.addObserver(this); } @@ -127,6 +132,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded); Log.d(TAG, String.format("onCreate() slotIndex=%d isExpanded=%b", mTrapezoidIndex, mIsExpanded)); + mMetricsFeatureProvider.action(mPrefContext, SettingsEnums.OPEN_BATTERY_USAGE); } @Override @@ -191,15 +197,22 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll final BatteryDiffEntry diffEntry = powerPref.getBatteryDiffEntry(); final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry; final String packageName = histEntry.mPackageName; + final boolean isAppEntry = histEntry.isAppEntry(); // Checks whether the package is installed or not. boolean isValidPackage = true; - if (histEntry.isAppEntry()) { + if (isAppEntry) { if (mBatteryUtils == null) { mBatteryUtils = BatteryUtils.getInstance(mPrefContext); } isValidPackage = mBatteryUtils.getPackageUid(packageName) != BatteryUtils.UID_NULL; } + mMetricsFeatureProvider.action( + mPrefContext, + isAppEntry + ? SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM + : SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM, + packageName); Log.d(TAG, String.format("handleClick() label=%s key=%s isValid:%b\n%s", diffEntry.getAppLabel(), histEntry.getKey(), isValidPackage, histEntry)); if (isValidPackage) { @@ -215,11 +228,20 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll public void onSelect(int trapezoidIndex) { Log.d(TAG, "onChartSelect:" + trapezoidIndex); refreshUi(trapezoidIndex, /*isForce=*/ false); + mMetricsFeatureProvider.action( + mPrefContext, + trapezoidIndex == BatteryChartView.SELECTED_INDEX_ALL + ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL + : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT); } @Override public void onExpand(boolean isExpanded) { mIsExpanded = isExpanded; + mMetricsFeatureProvider.action( + mPrefContext, + SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM, + isExpanded); refreshExpandUi(); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java index 3c371a068c8..43714727b9a 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartPreferenceControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.ContentValues; import android.content.pm.PackageManager; @@ -43,6 +44,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; @@ -82,12 +84,16 @@ public final class BatteryChartPreferenceControllerTest { @Mock private Resources mResources; private Context mContext; + private FakeFeatureFactory mFeatureFactory; private BatteryDiffEntry mBatteryDiffEntry; + private MetricsFeatureProvider mMetricsFeatureProvider; private BatteryChartPreferenceController mBatteryChartPreferenceController; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider; mContext = spy(RuntimeEnvironment.application); mBatteryChartPreferenceController = createController(); mBatteryChartPreferenceController.mPrefContext = mContext; @@ -331,9 +337,14 @@ public final class BatteryChartPreferenceControllerTest { } @Test - public void testHandlePreferenceTreeClick_notPowerGaugePreference_returnFalse() { + public void testHandlePreferenceTreeiClick_notPowerGaugePreference_returnFalse() { assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(mAppListGroup)) .isFalse(); + + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM); + verify(mMetricsFeatureProvider, never()) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM); } @Test @@ -343,6 +354,11 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick( mPowerGaugePreference)).isTrue(); + verify(mMetricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM, + mBatteryHistEntry.mPackageName); } @Test @@ -355,6 +371,11 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick( mPowerGaugePreference)).isFalse(); + verify(mMetricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BATTERY_USAGE_APP_ITEM, + mBatteryHistEntry.mPackageName); } @Test @@ -474,6 +495,11 @@ public final class BatteryChartPreferenceControllerTest { verify(mAppListGroup).addPreference(captor.capture()); // Verifies the added preference. assertThat(captor.getValue().getKey()).isEqualTo(PREF_KEY); + verify(mMetricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM, + true /*isExpanded*/); } @Test @@ -489,6 +515,28 @@ public final class BatteryChartPreferenceControllerTest { verify(mAppListGroup).findPreference(PREF_KEY); verify(mAppListGroup).removePreference(mPowerGaugePreference); assertThat(mBatteryChartPreferenceController.mPreferenceCache).hasSize(1); + verify(mMetricsFeatureProvider) + .action( + mContext, + SettingsEnums.ACTION_BATTERY_USAGE_EXPAND_ITEM, + false /*isExpanded*/); + } + + @Test + public void testOnSelect_selectSpecificTimeSlot_logMetric() { + mBatteryChartPreferenceController.onSelect(1 /*slot index*/); + + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT); + } + + @Test + public void testOnSelect_selectAll_logMetric() { + mBatteryChartPreferenceController.onSelect( + BatteryChartView.SELECTED_INDEX_ALL /*slot index*/); + + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL); } @Test @@ -610,6 +658,8 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.mTrapezoidIndex) .isEqualTo(expectedIndex); assertThat(mBatteryChartPreferenceController.mIsExpanded).isTrue(); + verify(mMetricsFeatureProvider) + .action(mContext, SettingsEnums.OPEN_BATTERY_USAGE); } @Test