From 770f4abf9de2bb7d74497cc4b5f6795023229ef2 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 31 Jul 2018 14:04:44 -0700 Subject: [PATCH 01/25] Disable changing lock when device is not provisioned. When the device is not yet provisioned and settings is launched: - disable the entry point for changing device lock - remove the search panel from settings home page - remove the search menu Bug: 110034419 Test: make RunSettingsRoboTests Change-Id: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 Merged-In: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 --- .../android/settings/SettingsActivity.java | 5 +- .../settings/password/ChooseLockGeneric.java | 9 +++ .../password/SetupChooseLockGeneric.java | 5 ++ .../actionbar/SearchMenuController.java | 4 ++ .../settings/SettingsActivityTest.java | 40 ++++++++++++ .../password/ChooseLockGenericTest.java | 65 +++++++++++++++++++ .../actionbar/SearchMenuControllerTest.java | 24 ++++++- 7 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 971ae040661..29cd77ade61 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -291,8 +291,10 @@ public class SettingsActivity extends SettingsDrawerActivity launchSettingFragment(initialFragmentName, isSubSettings, intent); } + final boolean deviceProvisioned = Utils.isDeviceProvisioned(this); if (mIsShowingDashboard) { - findViewById(R.id.search_bar).setVisibility(View.VISIBLE); + findViewById(R.id.search_bar).setVisibility( + deviceProvisioned ? View.VISIBLE : View.INVISIBLE); findViewById(R.id.action_bar).setVisibility(View.GONE); final Toolbar toolbar = findViewById(R.id.search_action_bar); FeatureFactory.getFactory(this).getSearchFeatureProvider() @@ -311,7 +313,6 @@ public class SettingsActivity extends SettingsDrawerActivity ActionBar actionBar = getActionBar(); if (actionBar != null) { - boolean deviceProvisioned = Utils.isDeviceProvisioned(this); actionBar.setDisplayHomeAsUpEnabled(deviceProvisioned); actionBar.setHomeButtonEnabled(deviceProvisioned); actionBar.setDisplayShowTitleEnabled(!mIsShowingDashboard); diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 62978b3089b..1a8afd02cc1 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -164,6 +164,11 @@ public class ChooseLockGeneric extends SettingsActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Activity activity = getActivity(); + if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + activity.finish(); + return; + } String chooseLockAction = getActivity().getIntent().getAction(); mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); @@ -249,6 +254,10 @@ public class ChooseLockGeneric extends SettingsActivity { addHeaderView(); } + protected boolean canRunBeforeDeviceProvisioned() { + return false; + } + protected void addHeaderView() { if (mForFingerprint) { setHeaderView(R.layout.choose_lock_generic_fingerprint_header); diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java index 179bd797cb7..885f9dfb420 100644 --- a/src/com/android/settings/password/SetupChooseLockGeneric.java +++ b/src/com/android/settings/password/SetupChooseLockGeneric.java @@ -129,6 +129,11 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); } + @Override + protected boolean canRunBeforeDeviceProvisioned() { + return true; + } + /*** * Disables preferences that are less secure than required quality and shows only secure * screen lock options here. diff --git a/src/com/android/settings/search/actionbar/SearchMenuController.java b/src/com/android/settings/search/actionbar/SearchMenuController.java index 1729ccdb39a..131f7884fc7 100644 --- a/src/com/android/settings/search/actionbar/SearchMenuController.java +++ b/src/com/android/settings/search/actionbar/SearchMenuController.java @@ -25,6 +25,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.SearchFeatureProvider; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -52,6 +53,9 @@ public class SearchMenuController implements LifecycleObserver, OnCreateOptionsM @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (!Utils.isDeviceProvisioned(mHost.getContext())) { + return; + } if (menu == null) { return; } diff --git a/tests/robotests/src/com/android/settings/SettingsActivityTest.java b/tests/robotests/src/com/android/settings/SettingsActivityTest.java index 2096629a63d..54b01eab76a 100644 --- a/tests/robotests/src/com/android/settings/SettingsActivityTest.java +++ b/tests/robotests/src/com/android/settings/SettingsActivityTest.java @@ -16,6 +16,7 @@ package com.android.settings; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -27,17 +28,25 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.FragmentManager; import android.app.FragmentTransaction; +import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.os.Bundle; +import android.provider.Settings.Global; +import android.view.View; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.SettingsShadowResourcesImpl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) public class SettingsActivityTest { @@ -49,15 +58,46 @@ public class SettingsActivityTest { @Mock private Bitmap mBitmap; private SettingsActivity mActivity; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; mActivity = spy(new SettingsActivity()); doReturn(mBitmap).when(mActivity).getBitmapFromXmlResource(anyInt()); } + @Test + @Config(shadows = { + SettingsShadowResourcesImpl.class, + SettingsShadowResources.SettingsShadowTheme.class, + }) + public void onCreate_deviceNotProvisioned_shouldDisableSearch() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + final Intent intent = new Intent(mContext, Settings.class); + final SettingsActivity activity = + Robolectric.buildActivity(SettingsActivity.class, intent).create(Bundle.EMPTY).get(); + + assertThat(activity.findViewById(R.id.search_bar).getVisibility()) + .isEqualTo(View.INVISIBLE); + } + + @Test + @Config(shadows = { + SettingsShadowResourcesImpl.class, + SettingsShadowResources.SettingsShadowTheme.class, + }) + public void onCreate_deviceProvisioned_shouldEnableSearch() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + final Intent intent = new Intent(mContext, Settings.class); + final SettingsActivity activity = + Robolectric.buildActivity(SettingsActivity.class, intent).create(Bundle.EMPTY).get(); + + assertThat(activity.findViewById(R.id.search_bar).getVisibility()).isEqualTo(View.VISIBLE); + } + @Test public void launchSettingFragment_nullExtraShowFragment_shouldNotCrash() { when(mActivity.getFragmentManager()).thenReturn(mFragmentManager); diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java new file mode 100644 index 00000000000..7a14896a8bb --- /dev/null +++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 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.password; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings.Global; + +import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ChooseLockGenericTest { + + @After + public void tearDown() { + Global.putInt(RuntimeEnvironment.application.getContentResolver(), + Global.DEVICE_PROVISIONED, 1); + } + + @Test + @Config(shadows = SettingsShadowResources.SettingsShadowTheme.class) + public void onCreate_deviceNotProvisioned_shouldFinishActivity() { + final Context context = RuntimeEnvironment.application; + Global.putInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + final Activity activity = mock(Activity.class); + when(activity.getContentResolver()).thenReturn(context.getContentResolver()); + when(activity.getTheme()).thenReturn(context.getTheme()); + + final ChooseLockGenericFragment fragment = spy(new ChooseLockGenericFragment()); + when(fragment.getActivity()).thenReturn(activity); + when(fragment.getArguments()).thenReturn(Bundle.EMPTY); + + fragment.onCreate(Bundle.EMPTY); + verify(activity).finish(); + } + +} diff --git a/tests/robotests/src/com/android/settings/search/actionbar/SearchMenuControllerTest.java b/tests/robotests/src/com/android/settings/search/actionbar/SearchMenuControllerTest.java index 7ff4accb178..9900df292bc 100644 --- a/tests/robotests/src/com/android/settings/search/actionbar/SearchMenuControllerTest.java +++ b/tests/robotests/src/com/android/settings/search/actionbar/SearchMenuControllerTest.java @@ -17,11 +17,14 @@ package com.android.settings.search.actionbar; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.content.Context; import android.os.Bundle; +import android.provider.Settings.Global; import android.view.Menu; import android.view.MenuItem; @@ -35,6 +38,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; @RunWith(SettingsRobolectricTestRunner.class) public class SearchMenuControllerTest { @@ -43,12 +47,16 @@ public class SearchMenuControllerTest { private Menu mMenu; private TestPreferenceFragment mPreferenceHost; private ObservableFragment mHost; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mHost = new ObservableFragment(); + mContext = RuntimeEnvironment.application; + mHost = spy(new ObservableFragment()); + when(mHost.getContext()).thenReturn(mContext); mPreferenceHost = new TestPreferenceFragment(); + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); when(mMenu.add(Menu.NONE, Menu.NONE, 0 /* order */, R.string.search_menu)) .thenReturn(mock(MenuItem.class)); @@ -81,9 +89,23 @@ public class SearchMenuControllerTest { verifyZeroInteractions(mMenu); } + @Test + public void init_deviceNotProvisioned_shouldNotAddMenu() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + SearchMenuController.init(mHost); + mHost.getLifecycle().onCreateOptionsMenu(mMenu, null /* inflater */); + + verifyZeroInteractions(mMenu); + } + private static class TestPreferenceFragment extends ObservablePreferenceFragment { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { } + + @Override + public Context getContext() { + return RuntimeEnvironment.application; + } } } From 94ea2dad5b936381d8ea0195ea7e63df956c0aed Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 1 Aug 2018 17:24:34 -0700 Subject: [PATCH 02/25] DO NOT MERGE Disable changing lock when device is not provisioned. When the device is not yet provisioned and settings is launched: - disable the entry point for changing device lock - set display search menu to false - disallow update to display the search menu Bug: 110034419 Test: make RunSettingsRoboTests Change-Id: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 --- src/com/android/settings/ChooseLockGeneric.java | 10 ++++++++++ src/com/android/settings/SettingsActivity.java | 4 ++-- src/com/android/settings/SetupChooseLockGeneric.java | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index 1a70f541b4e..4cc7b0e046f 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -51,6 +51,7 @@ import android.widget.TextView; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.Utils; import com.android.settings.fingerprint.FingerprintEnrollBase; import com.android.settings.fingerprint.FingerprintEnrollFindSensor; import com.android.settingslib.RestrictedLockUtils; @@ -140,6 +141,11 @@ public class ChooseLockGeneric extends SettingsActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Activity activity = getActivity(); + if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + activity.finish(); + return; + } String chooseLockAction = getActivity().getIntent().getAction(); mFingerprintManager = @@ -217,6 +223,10 @@ public class ChooseLockGeneric extends SettingsActivity { addHeaderView(); } + protected boolean canRunBeforeDeviceProvisioned() { + return false; + } + protected void addHeaderView() { if (mForFingerprint) { setHeaderView(R.layout.choose_lock_generic_fingerprint_header); diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index db9c0906efd..48d2303c4fc 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -626,7 +626,7 @@ public class SettingsActivity extends SettingsDrawerActivity // No UP affordance if we are displaying the main Dashboard mDisplayHomeAsUpEnabled = false; // Show Search affordance - mDisplaySearch = true; + mDisplaySearch = Utils.isDeviceProvisioned(this); mInitialTitleResId = R.string.dashboard_title; // add argument to indicate which settings tab should be initially selected @@ -708,7 +708,7 @@ public class SettingsActivity extends SettingsDrawerActivity } public void setDisplaySearchMenu(boolean displaySearch) { - if (displaySearch != mDisplaySearch) { + if (Utils.isDeviceProvisioned(this) && displaySearch != mDisplaySearch) { mDisplaySearch = displaySearch; invalidateOptionsMenu(); } diff --git a/src/com/android/settings/SetupChooseLockGeneric.java b/src/com/android/settings/SetupChooseLockGeneric.java index 71679f30a80..21fad53b8f5 100644 --- a/src/com/android/settings/SetupChooseLockGeneric.java +++ b/src/com/android/settings/SetupChooseLockGeneric.java @@ -138,6 +138,11 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); } + @Override + protected boolean canRunBeforeDeviceProvisioned() { + return true; + } + /*** * Disables preferences that are less secure than required quality and shows only secure * screen lock options here. From cd0b97e86f806f36440cc3a98d393a0fe2a072ca Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 1 Aug 2018 17:24:34 -0700 Subject: [PATCH 03/25] DO NOT MERGE Disable changing lock when device is not provisioned. When the device is not yet provisioned and settings is launched: - disable the entry point for changing device lock - set display search menu to false - disallow update to display the search menu Bug: 110034419 Test: make RunSettingsRoboTests Change-Id: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 --- src/com/android/settings/ChooseLockGeneric.java | 10 ++++++++++ src/com/android/settings/SettingsActivity.java | 4 ++-- src/com/android/settings/SetupChooseLockGeneric.java | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index 8d0e2ef2eca..fbff0bd12c5 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -51,6 +51,7 @@ import android.widget.TextView; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.Utils; import com.android.settings.fingerprint.FingerprintEnrollBase; import com.android.settings.fingerprint.FingerprintEnrollFindSensor; import com.android.settingslib.RestrictedLockUtils; @@ -140,6 +141,11 @@ public class ChooseLockGeneric extends SettingsActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Activity activity = getActivity(); + if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + activity.finish(); + return; + } String chooseLockAction = getActivity().getIntent().getAction(); mFingerprintManager = @@ -224,6 +230,10 @@ public class ChooseLockGeneric extends SettingsActivity { addHeaderView(); } + protected boolean canRunBeforeDeviceProvisioned() { + return false; + } + protected void addHeaderView() { if (mForFingerprint) { setHeaderView(R.layout.choose_lock_generic_fingerprint_header); diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 9245003de78..1b817fd66dc 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -626,7 +626,7 @@ public class SettingsActivity extends SettingsDrawerActivity // No UP affordance if we are displaying the main Dashboard mDisplayHomeAsUpEnabled = false; // Show Search affordance - mDisplaySearch = true; + mDisplaySearch = Utils.isDeviceProvisioned(this); mInitialTitleResId = R.string.dashboard_title; // add argument to indicate which settings tab should be initially selected @@ -708,7 +708,7 @@ public class SettingsActivity extends SettingsDrawerActivity } public void setDisplaySearchMenu(boolean displaySearch) { - if (displaySearch != mDisplaySearch) { + if (Utils.isDeviceProvisioned(this) && displaySearch != mDisplaySearch) { mDisplaySearch = displaySearch; invalidateOptionsMenu(); } diff --git a/src/com/android/settings/SetupChooseLockGeneric.java b/src/com/android/settings/SetupChooseLockGeneric.java index 71679f30a80..21fad53b8f5 100644 --- a/src/com/android/settings/SetupChooseLockGeneric.java +++ b/src/com/android/settings/SetupChooseLockGeneric.java @@ -138,6 +138,11 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); } + @Override + protected boolean canRunBeforeDeviceProvisioned() { + return true; + } + /*** * Disables preferences that are less secure than required quality and shows only secure * screen lock options here. From fff37ccb31d2b058b1bb2092ac55d88d6224a23e Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 1 Aug 2018 17:24:34 -0700 Subject: [PATCH 04/25] Disable changing lock when device is not provisioned. When the device is not yet provisioned and settings is launched: - disable the entry point for changing device lock - remove the search panel from settings home page Bug: 110034419 Test: make RunSettingsRoboTests Change-Id: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 Merged-In: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 --- .../android/settings/SettingsActivity.java | 8 +- .../settings/password/ChooseLockGeneric.java | 9 ++ .../password/SetupChooseLockGeneric.java | 5 + .../settings/SettingsActivityTest.java | 32 ++++-- .../password/ChooseLockGenericTest.java | 97 +++++++++++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 609120df0df..06bdd0cad46 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -322,7 +322,7 @@ public class SettingsActivity extends SettingsDrawerActivity } if (mIsShowingDashboard) { - findViewById(R.id.search_bar).setVisibility(View.VISIBLE); + setSearchBarVisibility(); findViewById(R.id.action_bar).setVisibility(View.GONE); Toolbar toolbar = findViewById(R.id.search_action_bar); toolbar.setOnClickListener(this); @@ -406,6 +406,12 @@ public class SettingsActivity extends SettingsDrawerActivity } } + @VisibleForTesting + void setSearchBarVisibility() { + findViewById(R.id.search_bar).setVisibility( + Utils.isDeviceProvisioned(this) ? View.VISIBLE : View.INVISIBLE); + } + @VisibleForTesting void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) { if (!mIsShowingDashboard && initialFragmentName != null) { diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index a694603fd95..081529ed89c 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -164,6 +164,11 @@ public class ChooseLockGeneric extends SettingsActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Activity activity = getActivity(); + if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + activity.finish(); + return; + } String chooseLockAction = getActivity().getIntent().getAction(); mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); @@ -248,6 +253,10 @@ public class ChooseLockGeneric extends SettingsActivity { addHeaderView(); } + protected boolean canRunBeforeDeviceProvisioned() { + return false; + } + protected void addHeaderView() { if (mForFingerprint) { setHeaderView(R.layout.choose_lock_generic_fingerprint_header); diff --git a/src/com/android/settings/password/SetupChooseLockGeneric.java b/src/com/android/settings/password/SetupChooseLockGeneric.java index 179bd797cb7..885f9dfb420 100644 --- a/src/com/android/settings/password/SetupChooseLockGeneric.java +++ b/src/com/android/settings/password/SetupChooseLockGeneric.java @@ -129,6 +129,11 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); } + @Override + protected boolean canRunBeforeDeviceProvisioned() { + return true; + } + /*** * Disables preferences that are less secure than required quality and shows only secure * screen lock options here. diff --git a/tests/robotests/src/com/android/settings/SettingsActivityTest.java b/tests/robotests/src/com/android/settings/SettingsActivityTest.java index 3fc46e86609..6a549fdddde 100644 --- a/tests/robotests/src/com/android/settings/SettingsActivityTest.java +++ b/tests/robotests/src/com/android/settings/SettingsActivityTest.java @@ -35,12 +35,11 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; -import android.view.Menu; +import android.provider.Settings.Global; +import android.view.View; import com.android.settings.search.SearchActivity; -import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,23 +55,44 @@ import org.robolectric.util.ReflectionHelpers; @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class SettingsActivityTest { - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Context mContext; - @Mock private FragmentManager mFragmentManager; @Mock private ActivityManager.TaskDescription mTaskDescription; @Mock private Bitmap mBitmap; + @Mock + private View mSearchBar; private SettingsActivity mActivity; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; mActivity = spy(new SettingsActivity()); doReturn(mBitmap).when(mActivity).getBitmapFromXmlResource(anyInt()); + doReturn(mContext.getContentResolver()).when(mActivity).getContentResolver(); + doReturn(mSearchBar).when(mActivity).findViewById(R.id.search_bar); + } + + @Test + public void setSearchBarVisibility_deviceNotProvisioned_shouldDisableSearch() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + + mActivity.setSearchBarVisibility(); + + verify(mSearchBar).setVisibility(View.INVISIBLE); + } + + @Test + public void setSearchBarVisibility_deviceProvisioned_shouldEnableSearch() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + + mActivity.setSearchBarVisibility(); + + verify(mSearchBar).setVisibility(View.VISIBLE); } @Test diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java new file mode 100644 index 00000000000..f1862e3cb86 --- /dev/null +++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 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.password; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.FragmentHostCallback; +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings.Global; + +import com.android.settings.TestConfig; +import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = { + SettingsShadowResources.class, + SettingsShadowResources.SettingsShadowTheme.class + }) +public class ChooseLockGenericTest { + + @Mock + private ChooseLockGeneric mActivity; + + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + when(mActivity.getContentResolver()).thenReturn(mContext.getContentResolver()); + when(mActivity.getTheme()).thenReturn(mContext.getTheme()); + when(mActivity.getResources()).thenReturn(mContext.getResources()); + } + + @After + public void tearDown() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + } + + @Test + public void onCreate_deviceNotProvisioned_shouldFinishActivity() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + final ChooseLockGenericFragment fragment = spy(new ChooseLockGenericFragment()); + when(fragment.getActivity()).thenReturn(mActivity); + ReflectionHelpers.setField(fragment, "mHost", new TestHostCallbacks()); + + fragment.onCreate(Bundle.EMPTY); + + verify(mActivity).finish(); + } + + public class TestHostCallbacks extends FragmentHostCallback { + + public TestHostCallbacks() { + super(mActivity, null /* handler */, 0 /* windowAnimations */); + } + + @Override + public Activity onGetHost() { + return mActivity; + } + } + +} From cb68db7d4f798a659dccf010c16ff34eba3adb7f Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 1 Aug 2018 17:24:34 -0700 Subject: [PATCH 05/25] DO NOT MERGE Disable changing lock when device is not provisioned. When the device is not yet provisioned and settings is launched: - disable the entry point for changing device lock - set display search menu to false - disallow update to display the search menu Bug: 110034419 Test: make RunSettingsRoboTests Change-Id: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 Merged-In: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 --- .../android/settings/ChooseLockGeneric.java | 9 ++ .../android/settings/SettingsActivity.java | 4 +- .../settings/SetupChooseLockGeneric.java | 5 + .../settings/ChooseLockGenericTest.java | 95 +++++++++++++++++++ .../settings/SettingsActivityTest.java | 31 +++++- 5 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/ChooseLockGenericTest.java diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index 49784f68130..ac064eab461 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -141,6 +141,11 @@ public class ChooseLockGeneric extends SettingsActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Activity activity = getActivity(); + if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + activity.finish(); + return; + } String chooseLockAction = getActivity().getIntent().getAction(); mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); @@ -218,6 +223,10 @@ public class ChooseLockGeneric extends SettingsActivity { addHeaderView(); } + protected boolean canRunBeforeDeviceProvisioned() { + return false; + } + protected void addHeaderView() { if (mForFingerprint) { setHeaderView(R.layout.choose_lock_generic_fingerprint_header); diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 4045fd2afdd..ea7874c6257 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -435,7 +435,7 @@ public class SettingsActivity extends SettingsDrawerActivity // No UP affordance if we are displaying the main Dashboard mDisplayHomeAsUpEnabled = false; // Show Search affordance - mDisplaySearch = true; + mDisplaySearch = Utils.isDeviceProvisioned(this); mInitialTitleResId = R.string.dashboard_title; switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false, @@ -444,7 +444,7 @@ public class SettingsActivity extends SettingsDrawerActivity } public void setDisplaySearchMenu(boolean displaySearch) { - if (displaySearch != mDisplaySearch) { + if (Utils.isDeviceProvisioned(this) && displaySearch != mDisplaySearch) { mDisplaySearch = displaySearch; invalidateOptionsMenu(); } diff --git a/src/com/android/settings/SetupChooseLockGeneric.java b/src/com/android/settings/SetupChooseLockGeneric.java index 2c8195d7075..0f516547e8b 100644 --- a/src/com/android/settings/SetupChooseLockGeneric.java +++ b/src/com/android/settings/SetupChooseLockGeneric.java @@ -131,6 +131,11 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); } + @Override + protected boolean canRunBeforeDeviceProvisioned() { + return true; + } + /*** * Disables preferences that are less secure than required quality and shows only secure * screen lock options here. diff --git a/tests/robotests/src/com/android/settings/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/ChooseLockGenericTest.java new file mode 100644 index 00000000000..e63d960b500 --- /dev/null +++ b/tests/robotests/src/com/android/settings/ChooseLockGenericTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 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; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.FragmentHostCallback; +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings.Global; + +import com.android.settings.ChooseLockGeneric.ChooseLockGenericFragment; +import com.android.settings.testutils.shadow.SettingsShadowResources; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + manifest = TestConfig.MANIFEST_PATH, + sdk = TestConfig.SDK_VERSION, + shadows = { + SettingsShadowResources.class, + SettingsShadowResources.SettingsShadowTheme.class + }) +public class ChooseLockGenericTest { + + @Mock + private ChooseLockGeneric mActivity; + + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + when(mActivity.getContentResolver()).thenReturn(mContext.getContentResolver()); + when(mActivity.getTheme()).thenReturn(mContext.getTheme()); + when(mActivity.getResources()).thenReturn(mContext.getResources()); + } + + @After + public void tearDown() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 1); + } + + @Test + public void onCreate_deviceNotProvisioned_shouldFinishActivity() { + Global.putInt(mContext.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + final ChooseLockGenericFragment fragment = spy(new ChooseLockGenericFragment()); + when(fragment.getActivity()).thenReturn(mActivity); + ReflectionHelpers.setField(fragment, "mHost", new TestHostCallbacks()); + + fragment.onCreate(Bundle.EMPTY); + + verify(mActivity).finish(); + } + + public class TestHostCallbacks extends FragmentHostCallback { + + public TestHostCallbacks() { + super(mActivity, null /* handler */, 0 /* windowAnimations */); + } + + @Override + public Activity onGetHost() { + return mActivity; + } + } + +} diff --git a/tests/robotests/src/com/android/settings/SettingsActivityTest.java b/tests/robotests/src/com/android/settings/SettingsActivityTest.java index 65e97083957..ba926a017af 100644 --- a/tests/robotests/src/com/android/settings/SettingsActivityTest.java +++ b/tests/robotests/src/com/android/settings/SettingsActivityTest.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; +import android.provider.Settings.Global; import android.view.Menu; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; @@ -80,11 +81,39 @@ public class SettingsActivityTest { when(mActivity.getFragmentManager()).thenReturn(mFragmentManager); when(mFragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class)); - doReturn(RuntimeEnvironment.application.getClassLoader()).when(mActivity).getClassLoader(); + final Context context = RuntimeEnvironment.application; + doReturn(context.getClassLoader()).when(mActivity).getClassLoader(); + doReturn(context.getContentResolver()).when(mActivity).getContentResolver(); mActivity.launchSettingFragment(null, true, mock(Intent.class)); } + @Test + public void launchSettingFragment_deviceNotProvisioned_shouldNotShowSearch() { + final Context context = RuntimeEnvironment.application; + Global.putInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + when(mActivity.getFragmentManager()).thenReturn(mFragmentManager); + when(mFragmentManager.beginTransaction()).thenReturn(mock(FragmentTransaction.class)); + doReturn(context.getClassLoader()).when(mActivity).getClassLoader(); + doReturn(context.getContentResolver()).when(mActivity).getContentResolver(); + + mActivity.launchSettingFragment(null, true, mock(Intent.class)); + + assertThat(mActivity.mDisplaySearch).isFalse(); + } + + @Test + public void setDisplaySearchMenu_deviceNotProvisioned_shouldNotUpdate() { + final Context context = RuntimeEnvironment.application; + Global.putInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0); + doReturn(context.getContentResolver()).when(mActivity).getContentResolver(); + mActivity.mDisplaySearch = false; + + mActivity.setDisplaySearchMenu(true); + + assertThat(mActivity.mDisplaySearch).isFalse(); + } + @Test public void testSetTaskDescription_IconChanged() { mActivity.setTaskDescription(mTaskDescription); From c624deaff5b00cf4e4dda87cb48b35200b2a1469 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 1 Aug 2018 17:24:34 -0700 Subject: [PATCH 06/25] DO NOT MERGE Disable changing lock when device is not provisioned. When the device is not yet provisioned and settings is launched: - disable the entry point for changing device lock - remove the search panel from settings home page Bug: 110034419 Test: make RunSettingsRoboTests Change-Id: Ieb7eb0e8699229ec0824ccc19d7b958ac44965a2 --- src/com/android/settings/ChooseLockGeneric.java | 9 +++++++++ src/com/android/settings/SettingsActivity.java | 2 +- src/com/android/settings/SetupChooseLockGeneric.java | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java index 5eb5132b334..fa9c935fcb6 100644 --- a/src/com/android/settings/ChooseLockGeneric.java +++ b/src/com/android/settings/ChooseLockGeneric.java @@ -133,6 +133,11 @@ public class ChooseLockGeneric extends SettingsActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Activity activity = getActivity(); + if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + activity.finish(); + return; + } mFingerprintManager = (FingerprintManager) getActivity().getSystemService(Context.FINGERPRINT_SERVICE); @@ -216,6 +221,10 @@ public class ChooseLockGeneric extends SettingsActivity { addHeaderView(); } + protected boolean canRunBeforeDeviceProvisioned() { + return false; + } + protected void addHeaderView() { if (mForFingerprint) { setHeaderView(R.layout.choose_lock_generic_fingerprint_header); diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index c597d16f5e0..52a7b846c11 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -619,7 +619,7 @@ public class SettingsActivity extends SettingsDrawerActivity // No UP affordance if we are displaying the main Dashboard mDisplayHomeAsUpEnabled = false; // Show Search affordance - mDisplaySearch = true; + mDisplaySearch = Utils.isDeviceProvisioned(this); mInitialTitleResId = R.string.dashboard_title; switchToFragment(DashboardSummary.class.getName(), null, false, false, mInitialTitleResId, mInitialTitle, false); diff --git a/src/com/android/settings/SetupChooseLockGeneric.java b/src/com/android/settings/SetupChooseLockGeneric.java index bc3a2ec0497..644a9c2d54d 100644 --- a/src/com/android/settings/SetupChooseLockGeneric.java +++ b/src/com/android/settings/SetupChooseLockGeneric.java @@ -147,6 +147,11 @@ public class SetupChooseLockGeneric extends ChooseLockGeneric { return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); } + @Override + protected boolean canRunBeforeDeviceProvisioned() { + return true; + } + /*** * Disables preferences that are less secure than required quality and shows only secure * screen lock options here. From 0a9f87667f3b58a3047655d91251ba388120e794 Mon Sep 17 00:00:00 2001 From: Zhaoyu Su Date: Mon, 9 Jul 2018 14:50:47 +0800 Subject: [PATCH 07/25] Fix ACTION_NFC_SETTINGS doesn't show connection preference NFC settings has been moved from "Device connection" to "Connection preference". So ACTION_NFC_SETTINGS should invoke "Connection preference" page. Test: send intent with action "android.settings.NFC_SETTINGS" Bug: 111424119 Change-Id: Iea9ddc876aa800f94585c434911be5dc8cedd550 --- AndroidManifest.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 27132165b91..6f80e111600 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -190,10 +190,6 @@ android:icon="@drawable/ic_homepage_connected_device" android:taskAffinity="com.android.settings" android:parentActivityName="Settings"> - - - - @@ -3264,6 +3260,10 @@ android:label="@string/connected_device_connections_title" android:taskAffinity="com.android.settings" android:parentActivityName="Settings$ConnectedDeviceDashboardActivity"> + + + + From fcf3f5861869cf331a33bd6a7d5e5218781dd474 Mon Sep 17 00:00:00 2001 From: Zimuzo Date: Wed, 1 Aug 2018 17:44:56 +0100 Subject: [PATCH 08/25] Add autofill service setting in managed profile Previously, there was no way to change the autofill service of the personal and managed profile independently. After 'uncloning' the setting in ag/4666330, we now introduce a separate UI control for each profile. BUG: 38033559 Test: Tested manually by setting up a work profile and verifying that the setting can be changed independently. Also verified that the additional UI does not show without a managed profile. Change-Id: I1c42fc4335bc319ca7f6fd1b7b10c781343ca248 --- res/xml/default_autofill_picker_settings.xml | 46 ++++++ res/xml/language_and_input.xml | 4 +- .../AutofillPickerTrampolineActivity.java | 4 +- .../defaultapps/AutofillPicker.java | 84 ++++++++++ .../DefaultAppPreferenceController.java | 6 +- .../defaultapps/DefaultAutofillPicker.java | 38 +++-- .../DefaultAutofillPreferenceController.java | 4 +- ...faultWorkAutofillPreferenceController.java | 82 ++++++++++ .../language/LanguageAndInputSettings.java | 9 +- .../DefaultAutofillPickerTest.java | 153 ++++++++++++++++-- 10 files changed, 390 insertions(+), 40 deletions(-) create mode 100644 res/xml/default_autofill_picker_settings.xml create mode 100644 src/com/android/settings/applications/defaultapps/AutofillPicker.java create mode 100644 src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java diff --git a/res/xml/default_autofill_picker_settings.xml b/res/xml/default_autofill_picker_settings.xml new file mode 100644 index 00000000000..26dff7eca87 --- /dev/null +++ b/res/xml/default_autofill_picker_settings.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index ec15f0d5268..50c6a824579 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -54,10 +54,10 @@ android:persistent="false" android:fragment="com.android.settings.inputmethod.SpellCheckersSettings" /> - diff --git a/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java b/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java index 1db3c82804f..9500fd5ae34 100644 --- a/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java +++ b/src/com/android/settings/applications/autofill/AutofillPickerTrampolineActivity.java @@ -16,6 +16,7 @@ package com.android.settings.applications.autofill; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.os.UserHandle; import android.view.autofill.AutofillManager; import com.android.settings.applications.defaultapps.DefaultAutofillPicker; @@ -36,7 +37,8 @@ public class AutofillPickerTrampolineActivity extends Activity { // First check if the current user's service already belongs to the app... final Intent intent = getIntent(); final String packageName = intent.getData().getSchemeSpecificPart(); - final String currentService = DefaultAutofillPicker.getDefaultKey(this); + final String currentService = DefaultAutofillPicker.getDefaultKey( + this, UserHandle.myUserId()); if (currentService != null && currentService.startsWith(packageName)) { // ...and succeed right away if it does. setResult(RESULT_OK); diff --git a/src/com/android/settings/applications/defaultapps/AutofillPicker.java b/src/com/android/settings/applications/defaultapps/AutofillPicker.java new file mode 100644 index 00000000000..3f392e59a08 --- /dev/null +++ b/src/com/android/settings/applications/defaultapps/AutofillPicker.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.applications.defaultapps; + +import android.content.Context; +import android.provider.SearchIndexableResource; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController; +import com.android.settings.applications.defaultapps.DefaultWorkAutofillPreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.search.SearchIndexable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class AutofillPicker extends DashboardFragment { + private static final String TAG = "AutofillPicker"; + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DEFAULT_AUTOFILL_PICKER; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.default_autofill_picker_settings; + } + + @Override + protected List createPreferenceControllers(Context context) { + return buildPreferenceControllers(context); + } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex(Context context, + boolean enabled) { + SearchIndexableResource searchIndexableResource = + new SearchIndexableResource(context); + searchIndexableResource.xmlResId = R.xml.default_autofill_picker_settings; + return Arrays.asList(searchIndexableResource); + } + + @Override + public List getPreferenceControllers(Context + context) { + return buildPreferenceControllers(context); + } + }; + + private static List buildPreferenceControllers(Context context) { + return Arrays.asList( + new DefaultAutofillPreferenceController(context), + new DefaultWorkAutofillPreferenceController(context)); + } +} diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java index 6016dbc118f..78248e67c93 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java @@ -82,12 +82,16 @@ public abstract class DefaultAppPreferenceController extends AbstractPreferenceC final Intent settingIntent = getSettingIntent(app); if (settingIntent != null) { ((GearPreference) preference).setOnGearClickListener( - p -> mContext.startActivity(settingIntent)); + p -> startActivity(settingIntent)); } else { ((GearPreference) preference).setOnGearClickListener(null); } } + protected void startActivity(Intent intent) { + mContext.startActivity(intent); + } + protected abstract DefaultAppInfo getDefaultAppInfo(); /** diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java index 9bb82d4fff0..1705dc578a6 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPicker.java @@ -27,25 +27,23 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Bundle; +import android.os.UserHandle; import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; import android.text.Html; import android.text.TextUtils; import android.util.Log; - +import androidx.preference.Preference; import com.android.internal.content.PackageMonitor; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settingslib.applications.DefaultAppInfo; import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.CandidateInfo; - import java.util.ArrayList; import java.util.List; -import androidx.preference.Preference; - public class DefaultAutofillPicker extends DefaultAppPickerFragment { private static final String TAG = "DefaultAutofillPicker"; @@ -73,8 +71,10 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { activity.setResult(Activity.RESULT_CANCELED); activity.finish(); }; + // If mCancelListener is not null, fragment is started from + // ACTION_REQUEST_SET_AUTOFILL_SERVICE and we should always use the calling uid. + mUserId = UserHandle.myUserId(); } - mSettingsPackageMonitor.register(activity, activity.getMainLooper(), false); update(); } @@ -159,8 +159,10 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { * @return The preference or {@code null} if no service can be added */ private Preference newAddServicePreferenceOrNull() { - final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(), - Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI); + final String searchUri = Settings.Secure.getStringForUser( + getActivity().getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI, + mUserId); if (TextUtils.isEmpty(searchUri)) { return null; } @@ -189,8 +191,8 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { @Override protected List getCandidates() { final List candidates = new ArrayList<>(); - final List resolveInfos = mPm.queryIntentServices( - AUTOFILL_PROBE, PackageManager.GET_META_DATA); + final List resolveInfos = mPm.queryIntentServicesAsUser( + AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId); final Context context = getContext(); for (ResolveInfo info : resolveInfos) { final String permission = info.serviceInfo.permission; @@ -210,8 +212,9 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { return candidates; } - public static String getDefaultKey(Context context) { - String setting = Settings.Secure.getString(context.getContentResolver(), SETTING); + public static String getDefaultKey(Context context, int userId) { + String setting = Settings.Secure.getStringForUser( + context.getContentResolver(), SETTING, userId); if (setting != null) { ComponentName componentName = ComponentName.unflattenFromString(setting); if (componentName != null) { @@ -223,7 +226,7 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { @Override protected String getDefaultKey() { - return getDefaultKey(getContext()); + return getDefaultKey(getContext(), mUserId); } @Override @@ -239,7 +242,7 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { @Override protected boolean setDefaultKey(String key) { - Settings.Secure.putString(getContext().getContentResolver(), SETTING, key); + Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING, key, mUserId); // Check if activity was launched from Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE // intent, and set proper result if so... @@ -263,16 +266,19 @@ public class DefaultAutofillPicker extends DefaultAppPickerFragment { private final String mSelectedKey; private final Context mContext; + private final int mUserId; - public AutofillSettingIntentProvider(Context context, String key) { + public AutofillSettingIntentProvider(Context context, int userId, String key) { mSelectedKey = key; mContext = context; + mUserId = userId; } @Override public Intent getIntent() { - final List resolveInfos = mContext.getPackageManager().queryIntentServices( - AUTOFILL_PROBE, PackageManager.GET_META_DATA); + final List resolveInfos = mContext.getPackageManager() + .queryIntentServicesAsUser( + AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId); for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; diff --git a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java index bab1d167df9..d32322b6fdb 100644 --- a/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java +++ b/src/com/android/settings/applications/defaultapps/DefaultAutofillPreferenceController.java @@ -44,7 +44,7 @@ public class DefaultAutofillPreferenceController extends DefaultAppPreferenceCon @Override public String getPreferenceKey() { - return "default_autofill"; + return "default_autofill_main"; } @Override @@ -54,7 +54,7 @@ public class DefaultAutofillPreferenceController extends DefaultAppPreferenceCon } final DefaultAutofillPicker.AutofillSettingIntentProvider intentProvider = new DefaultAutofillPicker.AutofillSettingIntentProvider( - mContext, info.getKey()); + mContext, mUserId, info.getKey()); return intentProvider.getIntent(); } diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java new file mode 100644 index 00000000000..ea4eff6b657 --- /dev/null +++ b/src/com/android/settings/applications/defaultapps/DefaultWorkAutofillPreferenceController.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 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.applications.defaultapps; + + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import com.android.settings.Utils; +import com.android.settingslib.applications.DefaultAppInfo; + +public class DefaultWorkAutofillPreferenceController extends DefaultAutofillPreferenceController { + private final UserHandle mUserHandle; + + public DefaultWorkAutofillPreferenceController(Context context) { + super(context); + mUserHandle = Utils.getManagedProfile(mUserManager); + } + + @Override + public boolean isAvailable() { + if (mUserHandle == null) { + return false; + } + return super.isAvailable(); + } + + @Override + public String getPreferenceKey() { + return "default_autofill_work"; + } + + @Override + protected DefaultAppInfo getDefaultAppInfo() { + final String flattenComponent = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + DefaultAutofillPicker.SETTING, + mUserHandle.getIdentifier()); + if (!TextUtils.isEmpty(flattenComponent)) { + DefaultAppInfo appInfo = new DefaultAppInfo( + mContext, + mPackageManager, + mUserHandle.getIdentifier(), + ComponentName.unflattenFromString(flattenComponent)); + return appInfo; + } + return null; + } + + @Override + protected Intent getSettingIntent(DefaultAppInfo info) { + if (info == null) { + return null; + } + final DefaultAutofillPicker.AutofillSettingIntentProvider intentProvider = + new DefaultAutofillPicker.AutofillSettingIntentProvider( + mContext, mUserHandle.getIdentifier(), info.getKey()); + return intentProvider.getIntent(); + } + + @Override + protected void startActivity(Intent intent) { + mContext.startActivityAsUser(intent, mUserHandle); + } +} diff --git a/src/com/android/settings/language/LanguageAndInputSettings.java b/src/com/android/settings/language/LanguageAndInputSettings.java index c983c071ba8..6c7c0d3861c 100644 --- a/src/com/android/settings/language/LanguageAndInputSettings.java +++ b/src/com/android/settings/language/LanguageAndInputSettings.java @@ -27,10 +27,10 @@ import android.speech.tts.TtsEngines; import android.text.TextUtils; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; -import com.android.settings.applications.defaultapps.DefaultAutofillPreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.inputmethod.PhysicalKeyboardPreferenceController; @@ -41,14 +41,10 @@ import com.android.settings.widget.PreferenceCategoryController; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - @SearchIndexable public class LanguageAndInputSettings extends DashboardFragment { @@ -122,7 +118,6 @@ public class LanguageAndInputSettings extends DashboardFragment { // Input Assistance controllers.add(new SpellCheckerPreferenceController(context)); - controllers.add(new DefaultAutofillPreferenceController(context)); return controllers; } diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java index ed823c113bd..dd57315365b 100644 --- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java +++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAutofillPickerTest.java @@ -17,65 +17,196 @@ package com.android.settings.applications.defaultapps; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.robolectric.RuntimeEnvironment.application; -import android.app.Activity; +import android.app.AppOpsManager; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.UserHandle; import android.os.UserManager; - +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceScreen; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.testutils.shadow.ShadowProcess; +import com.android.settings.testutils.shadow.ShadowSecureSettings; import com.android.settingslib.applications.DefaultAppInfo; - +import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = { + SettingsShadowResources.SettingsShadowTheme.class, + ShadowProcess.class, + ShadowSecureSettings.class + }) public class DefaultAutofillPickerTest { - private static final String TEST_APP_KEY = "foo.bar/foo.bar.Baz"; + private static final String MAIN_APP_KEY = "main.foo.bar/foo.bar.Baz"; + private static final String MANAGED_APP_KEY = "managed.foo.bar/foo.bar.Baz"; + private static final int MANAGED_PROFILE_UID = 10; + private static final int MAIN_PROFILE_UID = 0; @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Activity mActivity; + private FragmentActivity mActivity; @Mock private UserManager mUserManager; @Mock + private AppOpsManager mAppOpsManager; + @Mock private PackageManager mPackageManager; + @Mock + private PreferenceScreen mScreen; + private DefaultAutofillPicker mPicker; @Before public void setUp() { MockitoAnnotations.initMocks(this); FakeFeatureFactory.setupForTest(); + + Resources res = application.getResources(); + + when(mActivity.getApplicationContext()).thenReturn(mActivity); + when(mActivity.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mActivity.getTheme()).thenReturn(res.newTheme()); + when(mActivity.getResources()).thenReturn(res); + mPicker = spy(new DefaultAutofillPicker()); - mPicker.onAttach((Context) mActivity); + + doReturn(application.getApplicationContext()).when(mPicker).getContext(); + doReturn(mActivity).when(mPicker).getActivity(); + doReturn(res).when(mPicker).getResources(); + doReturn(mScreen).when(mPicker).getPreferenceScreen(); + + doNothing().when(mPicker).onCreatePreferences(any(), any()); + doNothing().when(mPicker).updateCandidates(); ReflectionHelpers.setField(mPicker, "mPm", mPackageManager); - - doReturn(RuntimeEnvironment.application).when(mPicker).getContext(); } @Test public void setAndGetDefaultAppKey_shouldUpdateDefaultAutoFill() { - assertThat(mPicker.setDefaultKey(TEST_APP_KEY)).isTrue(); - assertThat(mPicker.getDefaultKey()).isEqualTo(TEST_APP_KEY); + mPicker.onAttach((Context) mActivity); + + ReflectionHelpers.setField( + mPicker, "mUserId", MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE); + assertThat(mPicker.setDefaultKey(MAIN_APP_KEY)).isTrue(); + ReflectionHelpers.setField( + mPicker, "mUserId", MANAGED_PROFILE_UID * UserHandle.PER_USER_RANGE); + assertThat(mPicker.setDefaultKey(MANAGED_APP_KEY)).isTrue(); + + ReflectionHelpers.setField( + mPicker, "mUserId", MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE); + assertThat(mPicker.getDefaultKey()).isEqualTo(MAIN_APP_KEY); + ReflectionHelpers.setField( + mPicker, "mUserId", MANAGED_PROFILE_UID * UserHandle.PER_USER_RANGE); + assertThat(mPicker.getDefaultKey()).isEqualTo(MANAGED_APP_KEY); } @Test public void getConfirmationMessage_shouldNotBeNull() { + mPicker.onAttach((Context) mActivity); + final DefaultAppInfo info = mock(DefaultAppInfo.class); when(info.loadLabel()).thenReturn("test_app_name"); assertThat(mPicker.getConfirmationMessage(info)).isNotNull(); } + + @Test + public void mUserId_shouldDeriveUidFromManagedCaller() { + setupUserManager(); + setupCaller(); + ShadowProcess.setMyUid(MANAGED_PROFILE_UID * UserHandle.PER_USER_RANGE); + + mPicker.onAttach((Context) mActivity); + mPicker.onCreate(null); + + assertUserId(MANAGED_PROFILE_UID); + } + + @Test + public void mUserId_shouldDeriveUidFromMainCaller() { + setupUserManager(); + setupCaller(); + ShadowProcess.setMyUid(MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE); + + mPicker.onAttach((Context) mActivity); + mPicker.onCreate(null); + + assertUserId(MAIN_PROFILE_UID); + } + + @Test + public void mUserId_shouldDeriveUidFromManagedClick() { + setupUserManager(); + setupClick(/* forWork= */ true); + ShadowProcess.setMyUid(MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE); + + mPicker.onAttach((Context) mActivity); + mPicker.onCreate(null); + + assertUserId(MANAGED_PROFILE_UID); + } + + @Test + public void mUserId_shouldDeriveUidFromMainClick() { + setupUserManager(); + setupClick(/* forWork= */ false); + ShadowProcess.setMyUid(MAIN_PROFILE_UID * UserHandle.PER_USER_RANGE); + + mPicker.onAttach((Context) mActivity); + mPicker.onCreate(null); + + assertUserId(MAIN_PROFILE_UID); + } + + private void setupUserManager() { + UserHandle mainUserHandle = new UserHandle(MAIN_PROFILE_UID); + UserHandle managedUserHandle = new UserHandle(MANAGED_PROFILE_UID); + UserInfo managedUserInfo = new UserInfo( + MANAGED_PROFILE_UID, "managed", UserInfo.FLAG_MANAGED_PROFILE); + when(mUserManager.getUserProfiles()) + .thenReturn(Arrays.asList(mainUserHandle, managedUserHandle)); + when(mUserManager.getUserInfo(MANAGED_PROFILE_UID)) + .thenReturn(managedUserInfo); + when(mUserManager.getUserHandle()).thenReturn(MAIN_PROFILE_UID); + } + + private void setupCaller() { + Intent intent = new Intent(); + intent.putExtra("package_name", "any package name"); + when(mActivity.getIntent()).thenReturn(intent); + } + + private void setupClick(boolean forWork) { + Bundle bundle = new Bundle(); + bundle.putBoolean("for_work", forWork); + doReturn(bundle).when(mPicker).getArguments(); + } + + private void assertUserId(int userId) { + assertThat((Integer) ReflectionHelpers.getField(mPicker, "mUserId")) + .isEqualTo(userId); + } } From f546b97db989b162555803109b1cd05961218e96 Mon Sep 17 00:00:00 2001 From: Jordan Liu Date: Fri, 13 Jul 2018 10:43:47 -0700 Subject: [PATCH 09/25] Preserve leading 0s in mcc mnc Fixes: 79408450 Test: ApnEditorTest.java Change-Id: Iad7ffe04f23b30857588e50d7f5f0dd307bd2c6e Merged-In: Iad7ffe04f23b30857588e50d7f5f0dd307bd2c6e --- src/com/android/settings/network/ApnEditor.java | 12 +++++++++++- .../com/android/settings/network/ApnEditorTest.java | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/network/ApnEditor.java b/src/com/android/settings/network/ApnEditor.java index cceb31d29e7..5133d451bc8 100644 --- a/src/com/android/settings/network/ApnEditor.java +++ b/src/com/android/settings/network/ApnEditor.java @@ -313,12 +313,22 @@ public class ApnEditor extends SettingsPreferenceFragment static String formatInteger(String value) { try { final int intValue = Integer.parseInt(value); - return String.format("%d", intValue); + return String.format(getCorrectDigitsFormat(value), intValue); } catch (NumberFormatException e) { return value; } } + /** + * Get the digits format so we preserve leading 0's. + * MCCs are 3 digits and MNCs are either 2 or 3. + */ + static String getCorrectDigitsFormat(String value) { + if (value.length() == 2) return "%02d"; + else return "%03d"; + } + + /** * Check if passed in array of APN types indicates all APN types * @param apnTypes array of APN types. "*" indicates all types. diff --git a/tests/robotests/src/com/android/settings/network/ApnEditorTest.java b/tests/robotests/src/com/android/settings/network/ApnEditorTest.java index 35f68a06698..2fa9de12a01 100644 --- a/tests/robotests/src/com/android/settings/network/ApnEditorTest.java +++ b/tests/robotests/src/com/android/settings/network/ApnEditorTest.java @@ -440,6 +440,8 @@ public class ApnEditorTest { @Test public void formatInteger_shouldParseString() { assertThat(ApnEditor.formatInteger("42")).isEqualTo("42"); + assertThat(ApnEditor.formatInteger("01")).isEqualTo("01"); + assertThat(ApnEditor.formatInteger("001")).isEqualTo("001"); } @Test @@ -489,4 +491,4 @@ public class ApnEditorTest { mUri = uri; } } -} \ No newline at end of file +} From 25d9f3812b124524bee66f1f63f0b90b11f72856 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 7 Aug 2018 18:06:39 -0700 Subject: [PATCH 10/25] [Dynamic Home] Make a new page for all top level settings Since we are moving conditionals/suggestions to a different place, there is no need to use DashboardSummary to display top level settings any more. We can simplify a lot of code for top level settings and reduce it to a standard DashboardFragment. - Create a new DashboardFragment + xml for all top level internal items - Add a PreferenceController to provide summary for Network & internet item. - Mark a bunch of things deprecated for future work. Bug: 110405144 Test: robotests Change-Id: I9f778777131c28eb836b722e089e026a59f5ddc6 --- res/values/themes.xml | 1 + res/xml/top_level_settings.xml | 108 +++++++++++++++++ .../settings/dashboard/SummaryLoader.java | 6 + .../settings/homepage/HomepageFragment.java | 11 +- .../settings/homepage/TopLevelSettings.java | 110 ++++++++++++++++++ .../network/NetworkDashboardFragment.java | 1 + ...LevelNetworkEntryPreferenceController.java | 79 +++++++++++++ ...lNetworkEntryPreferenceControllerTest.java | 86 ++++++++++++++ 8 files changed, 397 insertions(+), 5 deletions(-) create mode 100644 res/xml/top_level_settings.xml create mode 100644 src/com/android/settings/homepage/TopLevelSettings.java create mode 100644 src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java diff --git a/res/values/themes.xml b/res/values/themes.xml index 63944d9bb34..dd711943940 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -170,6 +170,7 @@ @*android:color/primary_device_default_settings_light @*android:color/primary_dark_device_default_settings_light @*android:color/accent_device_default_light + @style/PreferenceTheme diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml new file mode 100644 index 00000000000..7aed02158cc --- /dev/null +++ b/res/xml/top_level_settings.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java index 199331d5b21..c85aad76b79 100644 --- a/src/com/android/settings/dashboard/SummaryLoader.java +++ b/src/com/android/settings/dashboard/SummaryLoader.java @@ -42,6 +42,12 @@ import com.android.settingslib.utils.ThreadUtils; import java.lang.reflect.Field; import java.util.List; +/** + * TODO(b/110405144): Remove this when all top level settings are converted to PreferenceControllers + * + * @deprecated + */ +@Deprecated public class SummaryLoader { private static final boolean DEBUG = DashboardSummary.DEBUG; private static final String TAG = "SummaryLoader"; diff --git a/src/com/android/settings/homepage/HomepageFragment.java b/src/com/android/settings/homepage/HomepageFragment.java index ff89dd5a4dc..2e22a035d9b 100644 --- a/src/com/android/settings/homepage/HomepageFragment.java +++ b/src/com/android/settings/homepage/HomepageFragment.java @@ -26,6 +26,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toolbar; +import androidx.annotation.NonNull; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.SettingsHomepageActivity; @@ -39,8 +41,6 @@ import com.google.android.material.bottomappbar.BottomAppBar; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import androidx.annotation.NonNull; - public class HomepageFragment extends InstrumentedFragment { private static final String TAG = "HomepageFragment"; @@ -70,7 +70,7 @@ public class HomepageFragment extends InstrumentedFragment { private void setupBottomBar() { final Activity activity = getActivity(); - mSearchButton = (FloatingActionButton) activity.findViewById(R.id.search_fab); + mSearchButton = activity.findViewById(R.id.search_fab); mSearchButton.setOnClickListener(v -> { final Intent intent = SearchFeatureProvider.SEARCH_UI_INTENT; @@ -79,7 +79,7 @@ public class HomepageFragment extends InstrumentedFragment { startActivityForResult(intent, 0 /* requestCode */); }); mBottomSheetBehavior = BottomSheetBehavior.from(activity.findViewById(R.id.bottom_sheet)); - final BottomAppBar bottomBar = (BottomAppBar) activity.findViewById(R.id.bar); + final BottomAppBar bottomBar = activity.findViewById(R.id.bar); bottomBar.setOnClickListener(v -> { mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); }); @@ -87,7 +87,7 @@ public class HomepageFragment extends InstrumentedFragment { final int screenWidthpx = getResources().getDisplayMetrics().widthPixels; final View searchbar = activity.findViewById(R.id.search_bar_container); final View bottombar = activity.findViewById(R.id.bar); - final Toolbar searchActionBar = (Toolbar) activity.findViewById(R.id.search_action_bar); + final Toolbar searchActionBar = activity.findViewById(R.id.search_action_bar); searchActionBar.setNavigationIcon(R.drawable.ic_search_floating_24dp); @@ -95,6 +95,7 @@ public class HomepageFragment extends InstrumentedFragment { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { if (!mBottomFragmentLoaded) { + // TODO(b/110405144): Switch to {@link TopLevelSettings} when it's ready. SettingsHomepageActivity.switchToFragment(getActivity(), R.id.bottom_sheet_fragment, DashboardSummary.class.getName()); mBottomFragmentLoaded = true; diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java new file mode 100644 index 00000000000..5c682cc12df --- /dev/null +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2018 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.homepage; + +import static com.android.settings.search.actionbar.SearchMenuController + .NEED_SEARCH_ICON_IN_ACTION_BAR; +import static com.android.settingslib.search.SearchIndexable.MOBILE; + +import android.content.Context; +import android.os.Bundle; +import android.provider.SearchIndexableResource; + +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +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 { + + private static final String TAG = "TopLevelSettings"; + + public TopLevelSettings() { + final Bundle args = new Bundle(); + // Disable the search icon because this page uses a full search view in actionbar. + args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false); + setArguments(args); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.top_level_settings; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DASHBOARD_SUMMARY; + } + + @Override + public int getHelpResource() { + // Disable the help icon because this page uses a full search view in actionbar. + return 0; + } + + @Override + public Fragment getCallbackFragment() { + return this; + } + + @Override + public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { + new SubSettingLauncher(getActivity()) + .setDestination(pref.getFragment()) + .setArguments(pref.getExtras()) + .setSourceMetricsCategory(caller instanceof Instrumentable + ? ((Instrumentable) caller).getMetricsCategory() + : Instrumentable.METRICS_CATEGORY_UNKNOWN) + .setTitleRes(-1) + .launch(); + return true; + } + + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.top_level_settings; + return Arrays.asList(sir); + } + + @Override + protected boolean isPageSearchEnabled(Context context) { + // Never searchable, all entries in this page are already indexed elsewhere. + return false; + } + }; +} diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index d5ef9aa0503..fbb0b20a543 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -149,6 +149,7 @@ public class NetworkDashboardFragment extends DashboardFragment implements return 0; } + // TODO(b/110405144): Remove SummaryProvider @VisibleForTesting static class SummaryProvider implements SummaryLoader.SummaryProvider { diff --git a/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java new file mode 100644 index 00000000000..567e52e596f --- /dev/null +++ b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 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.network; + +import android.content.Context; +import android.icu.text.ListFormatter; +import android.text.BidiFormatter; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.WifiMasterSwitchPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class TopLevelNetworkEntryPreferenceController extends BasePreferenceController { + + private final WifiMasterSwitchPreferenceController mWifiPreferenceController; + private final MobileNetworkPreferenceController mMobileNetworkPreferenceController; + private final TetherPreferenceController mTetherPreferenceController; + + public TopLevelNetworkEntryPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mMobileNetworkPreferenceController = new MobileNetworkPreferenceController(mContext); + mTetherPreferenceController = new TetherPreferenceController( + mContext, null /* lifecycle */); + mWifiPreferenceController = new WifiMasterSwitchPreferenceController( + mContext, null /* metrics */); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public CharSequence getSummary() { + final String wifiSummary = BidiFormatter.getInstance() + .unicodeWrap(mContext.getString(R.string.wifi_settings_title)); + final String mobileSummary = mContext.getString( + R.string.network_dashboard_summary_mobile); + final String dataUsageSummary = mContext.getString( + R.string.network_dashboard_summary_data_usage); + final String hotspotSummary = mContext.getString( + R.string.network_dashboard_summary_hotspot); + + final List summaries = new ArrayList<>(); + if (mWifiPreferenceController.isAvailable() + && !TextUtils.isEmpty(wifiSummary)) { + summaries.add(wifiSummary); + } + if (mMobileNetworkPreferenceController.isAvailable() && !TextUtils.isEmpty(mobileSummary)) { + summaries.add(mobileSummary); + } + if (!TextUtils.isEmpty(dataUsageSummary)) { + summaries.add(dataUsageSummary); + } + if (mTetherPreferenceController.isAvailable() + && !TextUtils.isEmpty(hotspotSummary)) { + summaries.add(hotspotSummary); + } + return ListFormatter.getInstance().format(summaries); + } +} diff --git a/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java new file mode 100644 index 00000000000..22aaf2a8aaf --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 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.network; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils; +import com.android.settings.wifi.WifiMasterSwitchPreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedLockUtils.class) +public class TopLevelNetworkEntryPreferenceControllerTest { + + @Mock + private WifiMasterSwitchPreferenceController mWifiPreferenceController; + @Mock + private MobileNetworkPreferenceController mMobileNetworkPreferenceController; + @Mock + private TetherPreferenceController mTetherPreferenceController; + + private Context mContext; + private TopLevelNetworkEntryPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new TopLevelNetworkEntryPreferenceController(mContext, "test_key"); + + ReflectionHelpers.setField(mController, "mWifiPreferenceController", + mWifiPreferenceController); + ReflectionHelpers.setField(mController, "mMobileNetworkPreferenceController", + mMobileNetworkPreferenceController); + ReflectionHelpers.setField(mController, "mTetherPreferenceController", + mTetherPreferenceController); + + } + + @Test + public void getSummary_hasMobileAndHotspot_shouldReturnMobileSummary() { + when(mWifiPreferenceController.isAvailable()).thenReturn(true); + when(mMobileNetworkPreferenceController.isAvailable()).thenReturn(true); + when(mTetherPreferenceController.isAvailable()).thenReturn(true); + + assertThat(mController.getSummary()) + .isEqualTo("Wi\u2011Fi, mobile, data usage, and hotspot"); + } + + @Test + public void getSummary_noMobileOrHotspot_shouldReturnSimpleSummary() { + when(mWifiPreferenceController.isAvailable()).thenReturn(true); + when(mMobileNetworkPreferenceController.isAvailable()).thenReturn(false); + when(mTetherPreferenceController.isAvailable()).thenReturn(false); + + assertThat(mController.getSummary()) + .isEqualTo("Wi\u2011Fi and data usage"); + } +} From fca684aab644cfd55e06e0f800ffee77575bb5cf Mon Sep 17 00:00:00 2001 From: Chienyuan Date: Wed, 8 Aug 2018 15:44:42 +0800 Subject: [PATCH 11/25] Replace LocalBluetoothAdapter with BluetoothAdapter LocalBluetoothAdapter is obsolete, use BluetoothAdapter instead. Bug: 111810977 Test: make -j50 RunSettingsRoboTests Change-Id: I5109a0296c1006a3c2e346bf966ef8901c101e30 --- ...ibilityHearingAidPreferenceController.java | 9 ++++----- ...ityHearingAidPreferenceControllerTest.java | 20 +++++++++---------- .../shadow/ShadowBluetoothAdapter.java | 14 +++++++++---- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java index b946e3ef532..b9713d7e8b0 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java @@ -39,7 +39,6 @@ import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; @@ -82,6 +81,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC }; private final LocalBluetoothManager mLocalBluetoothManager; + private final BluetoothAdapter mBluetoothAdapter; //cache value of supporting hearing aid or not private boolean mHearingAidProfileSupported; private FragmentManager mFragmentManager; @@ -89,6 +89,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mLocalBluetoothManager = getLocalBluetoothManager(); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mHearingAidProfileSupported = isHearingAidProfileSupported(); } @@ -151,8 +152,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC if (!mHearingAidProfileSupported) { return null; } - final LocalBluetoothAdapter localAdapter = mLocalBluetoothManager.getBluetoothAdapter(); - if (!localAdapter.isEnabled()) { + if (!mBluetoothAdapter.isEnabled()) { return null; } final List deviceList = mLocalBluetoothManager.getProfileManager() @@ -166,8 +166,7 @@ public class AccessibilityHearingAidPreferenceController extends BasePreferenceC } private boolean isHearingAidProfileSupported() { - final LocalBluetoothAdapter localAdapter = mLocalBluetoothManager.getBluetoothAdapter(); - final List supportedList = localAdapter.getSupportedProfiles(); + final List supportedList = mBluetoothAdapter.getSupportedProfiles(); if (supportedList.contains(BluetoothProfile.HEARING_AID)) { return true; } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java index 8957f8547ed..9fdcef58778 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java @@ -40,10 +40,10 @@ import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidProfile; -import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; @@ -55,23 +55,24 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothUtils.class}) +@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class}) public class AccessibilityHearingAidPreferenceControllerTest { private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; private static final String TEST_DEVICE_NAME = "TEST_HEARING_AID_BT_DEVICE_NAME"; private static final String HEARING_AID_PREFERENCE = "hearing_aid_preference"; private BluetoothAdapter mBluetoothAdapter; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; private BluetoothManager mBluetoothManager; private BluetoothDevice mBluetoothDevice; private Context mContext; private Preference mHearingAidPreference; - private List mProfileSupportedList; private AccessibilityHearingAidPreferenceController mPreferenceController; @Mock @@ -79,8 +80,6 @@ public class AccessibilityHearingAidPreferenceControllerTest { @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; @Mock - private LocalBluetoothAdapter mLocalBluetoothAdapter; - @Mock private LocalBluetoothManager mLocalBluetoothManager; @Mock private LocalBluetoothProfileManager mLocalBluetoothProfileManager; @@ -161,7 +160,7 @@ public class AccessibilityHearingAidPreferenceControllerTest { @Test public void onNotSupportHearingAidProfile_doNotDoReceiverOperation() { //clear bluetooth supported profile - mProfileSupportedList.clear(); + mShadowBluetoothAdapter.clearSupportedProfiles(); mPreferenceController = new AccessibilityHearingAidPreferenceController(mContext, HEARING_AID_PREFERENCE); mPreferenceController.setPreference(mHearingAidPreference); //not call registerReceiver() @@ -178,18 +177,17 @@ public class AccessibilityHearingAidPreferenceControllerTest { mLocalBluetoothManager = ShadowBluetoothUtils.getLocalBtManager(mContext); mBluetoothManager = new BluetoothManager(mContext); mBluetoothAdapter = mBluetoothManager.getAdapter(); - when(mLocalBluetoothManager.getBluetoothAdapter()).thenReturn(mLocalBluetoothAdapter); - when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true); when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); } private void setupHearingAidEnvironment() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS); - mProfileSupportedList = new ArrayList(); - mProfileSupportedList.add(BluetoothProfile.HEARING_AID); - when(mLocalBluetoothAdapter.getSupportedProfiles()).thenReturn(mProfileSupportedList); + mShadowBluetoothAdapter.enable(); + mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID); when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME); when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true); diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothAdapter.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothAdapter.java index 91afb87021f..30837f41451 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothAdapter.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowBluetoothAdapter.java @@ -30,13 +30,19 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto private String mName; private int mScanMode; private int mState; + private List mSupportedProfiles = new ArrayList(); - /** - * Do nothing, implement it to avoid null pointer error inside BluetoothAdapter - */ @Implementation public List getSupportedProfiles() { - return new ArrayList(); + return mSupportedProfiles; + } + + public void addSupportedProfiles(int profile) { + mSupportedProfiles.add(profile); + } + + public void clearSupportedProfiles() { + mSupportedProfiles.clear(); } public void setName(String name) { From fe50f43f3f4e2a1fc039afc5c0214c2677d9784d Mon Sep 17 00:00:00 2001 From: tmfang Date: Thu, 9 Aug 2018 12:48:58 +0800 Subject: [PATCH 12/25] Fix RequestPermissionActivity crash - Dialog needs to use AppCompat theme. - Activity needs to finish itself when user closed AlertDialog. If we don't fix it, you can see a window after AlertDialog was dismissed. Change-Id: Idfbd6b68bcdd3b577f1459657b635b7af9397276 Fixes: 112018696 Test: robo test, manual test --- res/values/themes.xml | 2 +- .../bluetooth/RequestPermissionActivity.java | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/res/values/themes.xml b/res/values/themes.xml index 63944d9bb34..0f150536abe 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -161,7 +161,7 @@ #00000000 - diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java index fff6f12ccc5..54854065e99 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -32,17 +32,17 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Log; +import androidx.appcompat.app.AlertDialog; + import com.android.settings.R; import com.android.settingslib.bluetooth.BluetoothDiscoverableTimeoutReceiver; -import androidx.appcompat.app.AlertDialog; - /** * RequestPermissionActivity asks the user whether to enable discovery. This is * usually started by an application wanted to start bluetooth and or discovery */ public class RequestPermissionActivity extends Activity implements - DialogInterface.OnClickListener { + DialogInterface.OnClickListener, DialogInterface.OnDismissListener { // Command line to test this // adb shell am start -a android.bluetooth.adapter.action.REQUEST_ENABLE // adb shell am start -a android.bluetooth.adapter.action.REQUEST_DISCOVERABLE @@ -188,6 +188,7 @@ public class RequestPermissionActivity extends Activity implements builder.setNegativeButton(getString(R.string.deny), this); } + builder.setOnDismissListener(this); mDialog = builder.create(); mDialog.show(); } @@ -238,12 +239,16 @@ public class RequestPermissionActivity extends Activity implements break; case DialogInterface.BUTTON_NEGATIVE: - setResult(RESULT_CANCELED); - finish(); + cancelAndFinish(); break; } } + @Override + public void onDismiss(final DialogInterface dialog) { + cancelAndFinish(); + } + private void proceedAndFinish() { int returnCode; From f20e34167e4d5b07772067f50486cb6fc6acf7c6 Mon Sep 17 00:00:00 2001 From: Pavel Grafov Date: Thu, 9 Aug 2018 16:51:55 +0100 Subject: [PATCH 13/25] Respect per-user fingerprints on profiles with unified challenge. When an app uses KeyguardManager.createConfirmDeviceCredentialIntent to ask the user to confirm credentials, it first goes into ConfirmDeviceCredentialActivity and then goes into ConfirmLockPattern/ConfirmLockPassword, that incorporates a derivative of ConfirmDeviceCredentialBaseFragment to deal with the actual credential and fingerprint checking. There are two bits of logic that are changed: 1) ConfirmDeviceCredentialBaseFragment gets target user id from the intent, then uses UserManager.getCredentialOwnerProfile to find the credential owner user id. If the target user is a work profile with unified challenge, profile owner will be primary user, otherwise it will be the same user. When credential confirmation dialog is invoked via KeyguardManager.createConfirmDeviceCredentialIntent, mUserId will already correspond to credential owner because ConfirmDeviceCredentialActivity already calls getCredentialOwnerUserId(), so real target user is not available. With this CL ConfirmDeviceCredentialActivity doesn't query credential owner because it will be handled later anyway. 2) Currently when confirming credentials for work profile with unified challenge we use mEffectiveUserId (credential owner) for fingerprints, which is incorrect, since fingerprints are per-user and primary profile fingerprints cannot unlock work profile apps' auth-bound keys. With this CL work profile user is used for fingerprints. Bug: 111821299 Test: manual, tried ConfirmCredential sample app in both profiles Test: manual, tried CA certificate installation in both profiles Test: manual, tried separate work challenge Change-Id: I074f773de1bd6207b01664f259bdd04766f32d41 --- .../settings/password/ConfirmDeviceCredentialActivity.java | 3 ++- .../settings/password/ConfirmDeviceCredentialBaseFragment.java | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java index 65d72f11bbc..f5b3b054c54 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java @@ -23,6 +23,7 @@ import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -70,7 +71,7 @@ public class ConfirmDeviceCredentialActivity extends Activity { KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction()); - int userId = Utils.getCredentialOwnerUserId(this); + int userId = UserHandle.myUserId(); if (isInternalActivity()) { try { userId = Utils.getUserIdFromBundle(this, intent.getExtras()); diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java index 0f6eeb3bb63..23bc26f3daf 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java @@ -125,8 +125,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr mCancelButton = (Button) view.findViewById(R.id.cancelButton); mFingerprintIcon = (ImageView) view.findViewById(R.id.fingerprintIcon); mFingerprintHelper = new FingerprintUiHelper( - mFingerprintIcon, - (TextView) view.findViewById(R.id.errorText), this, mEffectiveUserId); + mFingerprintIcon, view.findViewById(R.id.errorText), this, mUserId); boolean showCancelButton = getActivity().getIntent().getBooleanExtra( SHOW_CANCEL_BUTTON, false); boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText); From f6747a2f4459d83da71314fd043cc58ab6c6fe58 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 9 Aug 2018 09:51:35 -0700 Subject: [PATCH 14/25] Add summary text for more top level setting tiles - Connected devices - App & notifs - Battery Bug: 110405144 Test: robotests Change-Id: Ife44f7c5165483a1bad903ce90a241108ab5da25 --- res/xml/top_level_settings.xml | 25 ++-- src/com/android/settings/Settings.java | 1 - ...LevelAccountEntryPreferenceController.java | 68 ++++++++++ ...lConnectedDevicesPreferenceController.java | 40 ++++++ .../settings/dashboard/DashboardSummary.java | 16 ++- .../TopLevelStoragePreferenceController.java | 57 ++++++++ .../settings/fuelgauge/PowerUsageSummary.java | 37 ++--- .../TopLevelBatteryPreferenceController.java | 90 +++++++++++++ ...evelSecurityEntryPreferenceController.java | 52 ++++++++ .../AccountDashboardFragmentTest.java | 113 ---------------- ...lAccountEntryPreferenceControllerTest.java | 126 ++++++++++++++++++ ...nectedDevicesPreferenceControllerTest.java | 61 +++++++++ .../deviceinfo/StorageSettingsTest.java | 36 ----- ...pLevelStoragePreferenceControllerTest.java | 90 +++++++++++++ .../fuelgauge/PowerUsageSummaryTest.java | 19 +-- ...pLevelBatteryPreferenceControllerTest.java | 53 ++++++++ ...evelSecurityPreferenceControllerTest.java} | 53 +++----- 17 files changed, 700 insertions(+), 237 deletions(-) create mode 100644 src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java create mode 100644 src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceController.java create mode 100644 src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java create mode 100644 src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java create mode 100644 src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java rename tests/robotests/src/com/android/settings/security/{SecuritySettingsTest.java => TopLevelSecurityPreferenceControllerTest.java} (66%) diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index 7aed02158cc..171fe7867d6 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -34,12 +34,13 @@ android:title="@string/connected_devices_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_connected_device" - android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"/> + android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment" + settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/> @@ -48,19 +49,20 @@ android:title="@string/power_usage_summary_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_battery" - android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"/> + android:fragment="com.android.settings.fuelgauge.PowerUsageSummary" + settings:controller="com.android.settings.fuelgauge.TopLevelBatteryPreferenceController"/> @@ -69,33 +71,36 @@ android:title="@string/storage_settings" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_storage" - android:fragment="com.android.settings.deviceinfo.StorageSettings"/> + android:fragment="com.android.settings.deviceinfo.StorageSettings" + settings:controller="com.android.settings.deviceinfo.TopLevelStoragePreferenceController"/> + android:fragment="com.android.settings.security.SecuritySettings" + settings:controller="com.android.settings.security.TopLevelSecurityEntryPreferenceController"/> + android:fragment="com.android.settings.accounts.AccountDashboardFragment" + settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/> diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 4f011c19707..94de8da6e38 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -121,7 +121,6 @@ public class Settings extends SettingsActivity { } public static class DirectoryAccessSettingsActivity extends SettingsActivity { /* empty */ } - public static class TopLevelSettings extends SettingsActivity { /* empty */ } public static class ApnSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ } public static class MemorySettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java b/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java new file mode 100644 index 00000000000..a8d93d589d8 --- /dev/null +++ b/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 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.accounts; + +import android.content.Context; +import android.icu.text.ListFormatter; +import android.os.UserHandle; +import android.text.BidiFormatter; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.accounts.AuthenticatorHelper; + +import java.util.ArrayList; +import java.util.List; + +public class TopLevelAccountEntryPreferenceController extends BasePreferenceController { + public TopLevelAccountEntryPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public CharSequence getSummary() { + final AuthenticatorHelper authHelper = new AuthenticatorHelper(mContext, + UserHandle.of(UserHandle.myUserId()), null /* OnAccountsUpdateListener */); + final String[] types = authHelper.getEnabledAccountTypes(); + final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); + final List summaries = new ArrayList<>(); + + if (types == null || types.length == 0) { + summaries.add(mContext.getString(R.string.account_dashboard_default_summary)); + } else { + // Show up to 3 account types, ignore any null value + int accountToAdd = Math.min(3, types.length); + + for (int i = 0; i < types.length && accountToAdd > 0; i++) { + final CharSequence label = authHelper.getLabelForType(mContext, types[i]); + if (TextUtils.isEmpty(label)) { + continue; + } + + summaries.add(bidiFormatter.unicodeWrap(label)); + accountToAdd--; + } + } + return ListFormatter.getInstance().format(summaries); + } +} diff --git a/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceController.java b/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceController.java new file mode 100644 index 00000000000..6f16db6e368 --- /dev/null +++ b/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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.connecteddevice; + +import android.content.Context; + +import com.android.settings.core.BasePreferenceController; + +public class TopLevelConnectedDevicesPreferenceController extends BasePreferenceController { + + public TopLevelConnectedDevicesPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public CharSequence getSummary() { + return mContext.getText( + AdvancedConnectedDeviceController.getConnectedDevicesSummaryResourceId(mContext)); + } +} diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index c6b69e9953d..ec5f7dd4715 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -26,6 +26,11 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread; +import androidx.loader.app.LoaderManager; +import androidx.recyclerview.widget.LinearLayoutManager; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.core.InstrumentedFragment; @@ -46,11 +51,12 @@ import com.android.settingslib.utils.ThreadUtils; import java.util.List; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; -import androidx.loader.app.LoaderManager; -import androidx.recyclerview.widget.LinearLayoutManager; - +/** + * Deprecated in favor of {@link com.android.settings.homepage.TopLevelSettings} + * + * @deprecated + */ +@Deprecated public class DashboardSummary extends InstrumentedFragment implements CategoryListener, ConditionListener, FocusListener, SuggestionControllerMixinCompat.SuggestionControllerHost { diff --git a/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java b/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java new file mode 100644 index 00000000000..c6fc23b1df2 --- /dev/null +++ b/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceController.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 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.deviceinfo; + +import android.content.Context; +import android.os.storage.StorageManager; +import android.text.format.Formatter; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.deviceinfo.PrivateStorageInfo; +import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; + +import java.text.NumberFormat; + +public class TopLevelStoragePreferenceController extends BasePreferenceController { + + private final StorageManager mStorageManager; + private final StorageManagerVolumeProvider mStorageManagerVolumeProvider; + + public TopLevelStoragePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mStorageManager = mContext.getSystemService(StorageManager.class); + mStorageManagerVolumeProvider = new StorageManagerVolumeProvider(mStorageManager); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public CharSequence getSummary() { + // TODO: Register listener. + final NumberFormat percentageFormat = NumberFormat.getPercentInstance(); + final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo( + mStorageManagerVolumeProvider); + double privateUsedBytes = info.totalBytes - info.freeBytes; + return mContext.getString(R.string.storage_summary, + percentageFormat.format(privateUsedBytes / info.totalBytes), + Formatter.formatFileSize(mContext, info.freeBytes)); + } +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index 2ae58763ba4..75631206e53 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -17,13 +17,13 @@ package com.android.settings.fuelgauge; import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType; +import static com.android.settings.fuelgauge.TopLevelBatteryPreferenceController.getDashboardLabel; import android.app.Activity; import android.content.Context; import android.os.BatteryStats; import android.os.Bundle; import android.provider.SearchIndexableResource; -import android.text.BidiFormatter; import android.text.format.Formatter; import android.view.Menu; import android.view.MenuInflater; @@ -32,6 +32,11 @@ import android.view.View; import android.view.View.OnLongClickListener; import android.widget.TextView; +import androidx.annotation.VisibleForTesting; +import androidx.loader.app.LoaderManager; +import androidx.loader.app.LoaderManager.LoaderCallbacks; +import androidx.loader.content.Loader; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.SettingsActivity; @@ -51,11 +56,6 @@ import com.android.settingslib.utils.StringUtil; import java.util.Collections; import java.util.List; -import androidx.annotation.VisibleForTesting; -import androidx.loader.app.LoaderManager; -import androidx.loader.app.LoaderManager.LoaderCallbacks; -import androidx.loader.content.Loader; - /** * Displays a list of apps and subsystems that consume power, ordered by how much power was * consumed since the last time it was unplugged. @@ -147,9 +147,9 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList protected void updateViews(List batteryInfos) { final BatteryMeterView batteryView = mBatteryLayoutPref - .findViewById(R.id.battery_header_icon); + .findViewById(R.id.battery_header_icon); final TextView percentRemaining = - mBatteryLayoutPref.findViewById(R.id.battery_percent); + mBatteryLayoutPref.findViewById(R.id.battery_percent); final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1); final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2); BatteryInfo oldInfo = batteryInfos.get(0); @@ -160,13 +160,13 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList // can sometimes say 0 time remaining because battery stats requires the phone // be unplugged for a period of time before being willing ot make an estimate. summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString( - Formatter.formatShortElapsedTime(getContext(), - PowerUtil.convertUsToMs(oldInfo.remainingTimeUs)))); + Formatter.formatShortElapsedTime(getContext(), + PowerUtil.convertUsToMs(oldInfo.remainingTimeUs)))); // for this one we can just set the string directly summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString( - Formatter.formatShortElapsedTime(getContext(), - PowerUtil.convertUsToMs(newInfo.remainingTimeUs)))); + Formatter.formatShortElapsedTime(getContext(), + PowerUtil.convertUsToMs(newInfo.remainingTimeUs)))); batteryView.setBatteryLevel(oldInfo.batteryLevel); batteryView.setCharging(!oldInfo.discharging); @@ -419,19 +419,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList } } - @VisibleForTesting - static CharSequence getDashboardLabel(Context context, BatteryInfo info) { - CharSequence label; - final BidiFormatter formatter = BidiFormatter.getInstance(); - if (info.remainingLabel == null) { - label = info.batteryPercentString; - } else { - label = context.getString(R.string.power_remaining_settings_home_page, - formatter.unicodeWrap(info.batteryPercentString), - formatter.unicodeWrap(info.remainingLabel)); - } - return label; - } public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { diff --git a/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java new file mode 100644 index 00000000000..82058183e53 --- /dev/null +++ b/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceController.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import android.content.Context; +import android.text.BidiFormatter; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +public class TopLevelBatteryPreferenceController extends BasePreferenceController implements + LifecycleObserver, OnStart, OnStop { + + private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; + private Preference mPreference; + private BatteryInfo mBatteryInfo; + + public TopLevelBatteryPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); + mBatteryBroadcastReceiver.setBatteryChangedListener(type -> { + BatteryInfo.getBatteryInfo(mContext, info -> { + mBatteryInfo = info; + updateState(mPreference); + }, true /* shortString */); + }); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + mBatteryBroadcastReceiver.register(); + } + + @Override + public void onStop() { + mBatteryBroadcastReceiver.unRegister(); + } + + @Override + public CharSequence getSummary() { + return getDashboardLabel(mContext, mBatteryInfo); + } + + static CharSequence getDashboardLabel(Context context, BatteryInfo info) { + if (info == null || context == null) { + return null; + } + CharSequence label; + final BidiFormatter formatter = BidiFormatter.getInstance(); + if (info.remainingLabel == null) { + label = info.batteryPercentString; + } else { + label = context.getString(R.string.power_remaining_settings_home_page, + formatter.unicodeWrap(info.batteryPercentString), + formatter.unicodeWrap(info.remainingLabel)); + } + return label; + } +} diff --git a/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java b/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java new file mode 100644 index 00000000000..4b004240685 --- /dev/null +++ b/src/com/android/settings/security/TopLevelSecurityEntryPreferenceController.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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 android.content.Context; +import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; + +public class TopLevelSecurityEntryPreferenceController extends BasePreferenceController { + + public TopLevelSecurityEntryPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public CharSequence getSummary() { + final FingerprintManager fpm = + Utils.getFingerprintManagerOrNull(mContext); + final FaceManager faceManager = + Utils.getFaceManagerOrNull(mContext); + if (faceManager != null && faceManager.isHardwareDetected()) { + return mContext.getText(R.string.security_dashboard_summary_face); + } else if (fpm != null && fpm.isHardwareDetected()) { + return mContext.getText(R.string.security_dashboard_summary); + } else { + return mContext.getText(R.string.security_dashboard_summary_no_fingerprint); + } + } +} diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java index 40dcf7ad693..41ac4500b73 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountDashboardFragmentTest.java @@ -15,40 +15,20 @@ */ package com.android.settings.accounts; -import static com.android.settings.accounts.AccountDashboardFragmentTest - .ShadowAuthenticationHelper.LABELS; -import static com.android.settings.accounts.AccountDashboardFragmentTest - .ShadowAuthenticationHelper.TYPES; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import android.content.Context; -import android.os.UserHandle; import android.provider.SearchIndexableResource; -import android.text.TextUtils; -import com.android.settings.R; -import com.android.settings.dashboard.SummaryLoader; -import com.android.settings.testutils.Robolectric; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.drawer.CategoryKey; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; import java.util.List; -import androidx.fragment.app.FragmentActivity; - @RunWith(SettingsRobolectricTestRunner.class) public class AccountDashboardFragmentTest { @@ -59,66 +39,11 @@ public class AccountDashboardFragmentTest { mFragment = new AccountDashboardFragment(); } - @After - public void tearDown() { - ShadowAuthenticationHelper.reset(); - } - @Test public void testCategory_isAccount() { assertThat(mFragment.getCategoryKey()).isEqualTo(CategoryKey.CATEGORY_ACCOUNT); } - @Test - @Config(shadows = { - ShadowAuthenticationHelper.class - }) - public void updateSummary_hasAccount_shouldDisplayUpTo3AccountTypes() { - final SummaryLoader loader = mock(SummaryLoader.class); - final FragmentActivity activity = Robolectric.buildActivity( - FragmentActivity.class).setup().get(); - - final SummaryLoader.SummaryProvider provider = - AccountDashboardFragment.SUMMARY_PROVIDER_FACTORY.createSummaryProvider(activity, - loader); - provider.setListening(true); - - verify(loader).setSummary(provider, LABELS[0] + ", " + LABELS[1] + ", and " + LABELS[2]); - } - - @Test - @Config(shadows = ShadowAuthenticationHelper.class) - public void updateSummary_noAccount_shouldDisplayDefaultSummary() { - ShadowAuthenticationHelper.setEnabledAccount(null); - final SummaryLoader loader = mock(SummaryLoader.class); - final FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).setup().get(); - - final SummaryLoader.SummaryProvider provider = - AccountDashboardFragment.SUMMARY_PROVIDER_FACTORY.createSummaryProvider(activity, - loader); - provider.setListening(true); - - verify(loader).setSummary(provider, - activity.getString(R.string.account_dashboard_default_summary)); - } - - @Test - @Config(shadows = ShadowAuthenticationHelper.class) - public void updateSummary_noAccountTypeLabel_shouldNotDisplayNullEntry() { - final SummaryLoader loader = mock(SummaryLoader.class); - final FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).setup().get(); - final String[] enabledAccounts = {TYPES[0], "unlabeled_account_type", TYPES[1]}; - ShadowAuthenticationHelper.setEnabledAccount(enabledAccounts); - - final SummaryLoader.SummaryProvider provider = - AccountDashboardFragment.SUMMARY_PROVIDER_FACTORY.createSummaryProvider(activity, - loader); - provider.setListening(true); - - // should only show the 2 accounts with labels - verify(loader).setSummary(provider, LABELS[0] + " and " + LABELS[1]); - } - @Test public void testSearchIndexProvider_shouldIndexResource() { final List indexRes = @@ -129,43 +54,5 @@ public class AccountDashboardFragmentTest { assertThat(indexRes.get(0).xmlResId).isEqualTo(mFragment.getPreferenceScreenResId()); } - @Implements(AuthenticatorHelper.class) - public static class ShadowAuthenticationHelper { - static final String[] TYPES = {"type1", "type2", "type3", "type4"}; - static final String[] LABELS = {"LABEL1", "LABEL2", "LABEL3", "LABEL4"}; - private static String[] sEnabledAccount = TYPES; - - public void __constructor__(Context context, UserHandle userHandle, - AuthenticatorHelper.OnAccountsUpdateListener listener) { - } - - private static void setEnabledAccount(String[] enabledAccount) { - sEnabledAccount = enabledAccount; - } - - @Resetter - public static void reset() { - sEnabledAccount = TYPES; - } - - @Implementation - public String[] getEnabledAccountTypes() { - return sEnabledAccount; - } - - @Implementation - public CharSequence getLabelForType(Context context, final String accountType) { - if (TextUtils.equals(accountType, TYPES[0])) { - return LABELS[0]; - } else if (TextUtils.equals(accountType, TYPES[1])) { - return LABELS[1]; - } else if (TextUtils.equals(accountType, TYPES[2])) { - return LABELS[2]; - } else if (TextUtils.equals(accountType, TYPES[3])) { - return LABELS[3]; - } - return null; - } - } } diff --git a/tests/robotests/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceControllerTest.java new file mode 100644 index 00000000000..79e292dcf94 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accounts/TopLevelAccountEntryPreferenceControllerTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 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.accounts; + +import static com.android.settings.accounts.TopLevelAccountEntryPreferenceControllerTest + .ShadowAuthenticationHelper.LABELS; +import static com.android.settings.accounts.TopLevelAccountEntryPreferenceControllerTest + .ShadowAuthenticationHelper.TYPES; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.UserHandle; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.accounts.AuthenticatorHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {TopLevelAccountEntryPreferenceControllerTest.ShadowAuthenticationHelper.class}) +public class TopLevelAccountEntryPreferenceControllerTest { + + private TopLevelAccountEntryPreferenceController mController; + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mController = new TopLevelAccountEntryPreferenceController(mContext, "test_key"); + } + + @After + public void tearDown() { + ShadowAuthenticationHelper.reset(); + } + + @Test + + public void updateSummary_hasAccount_shouldDisplayUpTo3AccountTypes() { + assertThat(mController.getSummary()) + .isEqualTo(LABELS[0] + ", " + LABELS[1] + ", and " + LABELS[2]); + } + + @Test + public void updateSummary_noAccount_shouldDisplayDefaultSummary() { + ShadowAuthenticationHelper.setEnabledAccount(null); + + assertThat(mController.getSummary()).isEqualTo( + mContext.getText(R.string.account_dashboard_default_summary)); + } + + @Test + public void updateSummary_noAccountTypeLabel_shouldNotDisplayNullEntry() { + final String[] enabledAccounts = {TYPES[0], "unlabeled_account_type", TYPES[1]}; + ShadowAuthenticationHelper.setEnabledAccount(enabledAccounts); + + + // should only show the 2 accounts with labels + assertThat(mController.getSummary()).isEqualTo(LABELS[0] + " and " + LABELS[1]); + } + + @Implements(AuthenticatorHelper.class) + public static class ShadowAuthenticationHelper { + + static final String[] TYPES = {"type1", "type2", "type3", "type4"}; + static final String[] LABELS = {"LABEL1", "LABEL2", "LABEL3", "LABEL4"}; + private static String[] sEnabledAccount = TYPES; + + public void __constructor__(Context context, UserHandle userHandle, + AuthenticatorHelper.OnAccountsUpdateListener listener) { + } + + private static void setEnabledAccount(String[] enabledAccount) { + sEnabledAccount = enabledAccount; + } + + @Resetter + public static void reset() { + sEnabledAccount = TYPES; + } + + @Implementation + public String[] getEnabledAccountTypes() { + return sEnabledAccount; + } + + @Implementation + public CharSequence getLabelForType(Context context, final String accountType) { + if (TextUtils.equals(accountType, TYPES[0])) { + return LABELS[0]; + } else if (TextUtils.equals(accountType, TYPES[1])) { + return LABELS[1]; + } else if (TextUtils.equals(accountType, TYPES[2])) { + return LABELS[2]; + } else if (TextUtils.equals(accountType, TYPES[3])) { + return LABELS[3]; + } + return null; + } + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceControllerTest.java new file mode 100644 index 00000000000..8816bec88f3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/TopLevelConnectedDevicesPreferenceControllerTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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.connecteddevice; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@RunWith(SettingsRobolectricTestRunner.class) +public class TopLevelConnectedDevicesPreferenceControllerTest { + + private Context mContext; + private TopLevelConnectedDevicesPreferenceController mController; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mController = new TopLevelConnectedDevicesPreferenceController(mContext, "test_key"); + } + + @Test + @Config(shadows = ShadowAdvancedConnectedDeviceController.class) + public void getSummary_shouldCallAdvancedConnectedDeviceController() { + assertThat(mController.getSummary()) + .isEqualTo(mContext.getText(R.string.settings_label_launcher)); + } + + @Implements(AdvancedConnectedDeviceController.class) + private static class ShadowAdvancedConnectedDeviceController { + + @Implementation + public static int getConnectedDevicesSummaryResourceId(Context context) { + return R.string.settings_label_launcher; + } + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java index 943bd9d2630..cb02c76846f 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageSettingsTest.java @@ -17,7 +17,6 @@ package com.android.settings.deviceinfo; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -25,14 +24,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; -import android.app.usage.StorageStatsManager; import android.content.Intent; -import android.icu.text.NumberFormat; import android.os.storage.VolumeInfo; -import android.text.format.Formatter; -import com.android.settings.R; -import com.android.settings.dashboard.SummaryLoader; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; @@ -41,8 +35,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.List; @@ -65,34 +57,6 @@ public class StorageSettingsTest { when(mStorageManagerVolumeProvider.getVolumes()).thenReturn(mVolumes); } - @Test - public void updateSummary_shouldDisplayUsedPercentAndFreeSpace() throws Exception { - final SummaryLoader loader = mock(SummaryLoader.class); - final SummaryLoader.SummaryProvider provider = - StorageSettings.SUMMARY_PROVIDER_FACTORY.createSummaryProvider(mActivity, loader); - final VolumeInfo volumeInfo = mVolumes.get(0); - when(volumeInfo.isMountedReadable()).thenReturn(true); - when(volumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); - when(mStorageManagerVolumeProvider - .getTotalBytes(nullable(StorageStatsManager.class), nullable(VolumeInfo.class))) - .thenReturn(500L); - when(mStorageManagerVolumeProvider - .getFreeBytes(nullable(StorageStatsManager.class), nullable(VolumeInfo.class))) - .thenReturn(0L); - - ReflectionHelpers - .setField(provider, "mStorageManagerVolumeProvider", mStorageManagerVolumeProvider); - ReflectionHelpers.setField(provider, "mContext", RuntimeEnvironment.application); - - provider.setListening(true); - - final String percentage = NumberFormat.getPercentInstance().format(1); - final String freeSpace = Formatter.formatFileSize(RuntimeEnvironment.application, 0); - verify(loader).setSummary(provider, - RuntimeEnvironment.application.getString( - R.string.storage_summary, percentage, freeSpace)); - } - @Test public void handlePublicVolumeClick_startsANonNullActivityWhenVolumeHasNoBrowse() { VolumeInfo volumeInfo = mock(VolumeInfo.class, RETURNS_DEEP_STUBS); diff --git a/tests/robotests/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java new file mode 100644 index 00000000000..00484df3efc --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/TopLevelStoragePreferenceControllerTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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.deviceinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.usage.StorageStatsManager; +import android.content.Context; +import android.icu.text.NumberFormat; +import android.os.storage.VolumeInfo; +import android.text.format.Formatter; + +import com.android.settings.R; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +public class TopLevelStoragePreferenceControllerTest { + + @Mock + private StorageManagerVolumeProvider mStorageManagerVolumeProvider; + + private Context mContext; + private TopLevelStoragePreferenceController mController; + private List mVolumes; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mVolumes = new ArrayList<>(); + mVolumes.add(mock(VolumeInfo.class, RETURNS_DEEP_STUBS)); + when(mStorageManagerVolumeProvider.getVolumes()).thenReturn(mVolumes); + + mController = new TopLevelStoragePreferenceController(mContext, "test_key"); + } + + @Test + public void updateSummary_shouldDisplayUsedPercentAndFreeSpace() throws Exception { + final VolumeInfo volumeInfo = mVolumes.get(0); + when(volumeInfo.isMountedReadable()).thenReturn(true); + when(volumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE); + when(mStorageManagerVolumeProvider + .getTotalBytes(nullable(StorageStatsManager.class), nullable(VolumeInfo.class))) + .thenReturn(500L); + when(mStorageManagerVolumeProvider + .getFreeBytes(nullable(StorageStatsManager.class), nullable(VolumeInfo.class))) + .thenReturn(0L); + + ReflectionHelpers.setField(mController, + "mStorageManagerVolumeProvider", mStorageManagerVolumeProvider); + + final String percentage = NumberFormat.getPercentInstance().format(1); + final String freeSpace = Formatter.formatFileSize(RuntimeEnvironment.application, 0); + assertThat(mController.getSummary()).isEqualTo( + RuntimeEnvironment.application.getString( + R.string.storage_summary, percentage, freeSpace)); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index 68d9994cf9c..cf1a5f346d7 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -16,7 +16,9 @@ package com.android.settings.fuelgauge; import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADVANCED_BATTERY; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; @@ -40,6 +42,8 @@ import android.view.MenuItem; import android.view.View; import android.widget.TextView; +import androidx.loader.app.LoaderManager; + import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; @@ -50,7 +54,6 @@ import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.XmlTestUtils; import com.android.settings.testutils.shadow.SettingsShadowResources; -import com.android.settingslib.core.AbstractPreferenceController; import org.junit.Before; import org.junit.BeforeClass; @@ -69,8 +72,6 @@ import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.List; -import androidx.loader.app.LoaderManager; - // TODO: Improve this test class so that it starts up the real activity and fragment. @RunWith(SettingsRobolectricTestRunner.class) @Config(shadows = { @@ -343,18 +344,6 @@ public class PowerUsageSummaryTest { verify(mFragment).restartBatteryTipLoader(); } - @Test - public void getDashboardLabel_returnsCorrectLabel() { - BatteryInfo info = new BatteryInfo(); - info.batteryPercentString = "3%"; - assertThat(PowerUsageSummary.getDashboardLabel(mRealContext, info)) - .isEqualTo(info.batteryPercentString); - - info.remainingLabel = "Phone will shut down soon"; - assertThat(PowerUsageSummary.getDashboardLabel(mRealContext, info)) - .isEqualTo("3% - Phone will shut down soon"); - } - public static class TestFragment extends PowerUsageSummary { private Context mContext; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java new file mode 100644 index 00000000000..b1bc074eb50 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/TopLevelBatteryPreferenceControllerTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import static com.android.settings.fuelgauge.TopLevelBatteryPreferenceController.getDashboardLabel; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class TopLevelBatteryPreferenceControllerTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + @Test + public void getDashboardLabel_returnsCorrectLabel() { + BatteryInfo info = new BatteryInfo(); + info.batteryPercentString = "3%"; + assertThat(getDashboardLabel(mContext, info)) + .isEqualTo(info.batteryPercentString); + + info.remainingLabel = "Phone will shut down soon"; + assertThat(getDashboardLabel(mContext, info)) + .isEqualTo("3% - Phone will shut down soon"); + } +} diff --git a/tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java b/tests/robotests/src/com/android/settings/security/TopLevelSecurityPreferenceControllerTest.java similarity index 66% rename from tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java rename to tests/robotests/src/com/android/settings/security/TopLevelSecurityPreferenceControllerTest.java index f3cc4593ff5..17ba6d5f3f2 100644 --- a/tests/robotests/src/com/android/settings/security/SecuritySettingsTest.java +++ b/tests/robotests/src/com/android/settings/security/TopLevelSecurityPreferenceControllerTest.java @@ -17,7 +17,6 @@ package com.android.settings.security; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -26,7 +25,6 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import com.android.settings.R; -import com.android.settings.dashboard.SummaryLoader; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; @@ -37,17 +35,15 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(SettingsRobolectricTestRunner.class) -public class SecuritySettingsTest { +public class TopLevelSecurityPreferenceControllerTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @Mock - private SummaryLoader mSummaryLoader; - @Mock private FingerprintManager mFingerprintManager; @Mock private FaceManager mFaceManager; - private SecuritySettings.SummaryProvider mSummaryProvider; + private TopLevelSecurityEntryPreferenceController mController; @Before public void setUp() { @@ -56,87 +52,80 @@ public class SecuritySettingsTest { .thenReturn(mFingerprintManager); when(mContext.getSystemService(Context.FACE_SERVICE)) .thenReturn(mFaceManager); - mSummaryProvider = new SecuritySettings.SummaryProvider(mContext, mSummaryLoader); + mController = new TopLevelSecurityEntryPreferenceController(mContext, "test_key"); } @Test - public void testSummaryProvider_notListening() { - mSummaryProvider.setListening(false); - - verifyNoMoreInteractions(mSummaryLoader); - } - - @Test - public void testSummaryProvider_hasFace_hasStaticSummary() { + public void geSummary_hasFace_hasStaticSummary() { when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(true); when(mFaceManager.isHardwareDetected()).thenReturn(true); - mSummaryProvider.setListening(true); + mController.getSummary(); - verify(mContext).getString(R.string.security_dashboard_summary_face); + verify(mContext).getText(R.string.security_dashboard_summary_face); } @Test - public void testSummaryProvider_hasFingerPrint_hasStaticSummary() { + public void geSummary_hasFingerPrint_hasStaticSummary() { when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(false); when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) .thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - mSummaryProvider.setListening(true); + mController.getSummary(); - verify(mContext).getString(R.string.security_dashboard_summary); + verify(mContext).getText(R.string.security_dashboard_summary); } @Test - public void testSummaryProvider_noFpFeature_shouldSetSummaryWithNoBiometrics() { + public void geSummary_noFpFeature_shouldSetSummaryWithNoBiometrics() { when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) .thenReturn(false); when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(false); - mSummaryProvider.setListening(true); + mController.getSummary(); - verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint); + verify(mContext).getText(R.string.security_dashboard_summary_no_fingerprint); } @Test - public void testSummaryProvider_noFpHardware_shouldSetSummaryWithNoBiometrics() { + public void geSummary_noFpHardware_shouldSetSummaryWithNoBiometrics() { when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(false); when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) .thenReturn(true); when(mFingerprintManager.isHardwareDetected()).thenReturn(false); - mSummaryProvider.setListening(true); + mController.getSummary(); - verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint); + verify(mContext).getText(R.string.security_dashboard_summary_no_fingerprint); } @Test - public void testSummaryProvider_noFaceFeature_shouldSetSummaryWithNoBiometrics() { + public void geSummary_noFaceFeature_shouldSetSummaryWithNoBiometrics() { when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) .thenReturn(false); when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(false); - mSummaryProvider.setListening(true); + mController.getSummary(); - verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint); + verify(mContext).getText(R.string.security_dashboard_summary_no_fingerprint); } @Test - public void testSummaryProvider_noFaceHardware_shouldSetSummaryWithNoBiometrics() { + public void geSummary_noFaceHardware_shouldSetSummaryWithNoBiometrics() { when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) .thenReturn(true); when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) .thenReturn(false); when(mFaceManager.isHardwareDetected()).thenReturn(false); - mSummaryProvider.setListening(true); + mController.getSummary(); - verify(mContext).getString(R.string.security_dashboard_summary_no_fingerprint); + verify(mContext).getText(R.string.security_dashboard_summary_no_fingerprint); } } From 22b4edd79ee19b84c09c74ec5d32ddfc3d12cc87 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Mon, 30 Jul 2018 19:20:01 -0700 Subject: [PATCH 15/25] 6/n: Add camera preview to FaceEnrollEnrolling Bug: 112005540 Test: manual Change-Id: Ie4f810dffecdec9731e20d5756854d9c9f420f4b --- AndroidManifest.xml | 15 +- res/layout/face_enroll_enrolling.xml | 23 +- .../face/FaceEnrollAnimationDrawable.java | 85 +++++ .../biometrics/face/FaceEnrollEnrolling.java | 14 + .../face/FaceEnrollPreviewFragment.java | 347 ++++++++++++++++++ .../face/FaceSquareFrameLayout.java | 60 +++ .../face/FaceSquareTextureView.java | 52 +++ 7 files changed, 583 insertions(+), 13 deletions(-) create mode 100644 src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java create mode 100644 src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java create mode 100644 src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java create mode 100644 src/com/android/settings/biometrics/face/FaceSquareTextureView.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0a5f24ba1be..51de8024d8a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -94,6 +94,7 @@ + - - - + + + + + diff --git a/res/layout/face_enroll_enrolling.xml b/res/layout/face_enroll_enrolling.xml index 6ced80f0413..2208cc264d7 100644 --- a/res/layout/face_enroll_enrolling.xml +++ b/res/layout/face_enroll_enrolling.xml @@ -39,20 +39,23 @@ android:gravity="center" android:orientation="vertical"> - - - + - + + + bigEnough = new ArrayList<>(); + // Collect the supported resolutions that are smaller than the preview Surface + List notBigEnough = new ArrayList<>(); + + for (Size option : choices) { + if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && + option.getHeight() == option.getWidth()) { + if (option.getWidth() >= textureViewWidth && + option.getHeight() >= textureViewHeight) { + bigEnough.add(option); + } else { + notBigEnough.add(option); + } + } + } + + // Pick the smallest of those big enough. If there is no one big enough, pick the + // largest of those not big enough. + if (bigEnough.size() > 0) { + return Collections.min(bigEnough, new CompareSizesByArea()); + } else if (notBigEnough.size() > 0) { + return Collections.max(notBigEnough, new CompareSizesByArea()); + } else { + Log.e(TAG, "Couldn't find any suitable preview size"); + return choices[0]; + } + } + + /** + * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. + * This method should be called after the camera preview size is determined in + * setUpCameraOutputs and also the size of `mTextureView` is fixed. + * + * @param viewWidth The width of `mTextureView` + * @param viewHeight The height of `mTextureView` + */ + private void configureTransform(int viewWidth, int viewHeight) { + if (mTextureView == null) { + return; + } + + // Fix the aspect ratio + Matrix matrix = new Matrix(); + float scaleX = (float) viewWidth / mPreviewSize.getWidth(); + float scaleY = (float) viewHeight / mPreviewSize.getHeight(); + + // Now divide by smaller one so it fills up the original space + float smaller = Math.min(scaleX, scaleY); + scaleX = scaleX / smaller; + scaleY = scaleY / smaller; + + // Apply the scale + matrix.setScale(scaleX, scaleY); + + mTextureView.setTransform(matrix); + } + + private void closeCamera() { + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + if (mCameraDevice != null) { + mCameraDevice.close(); + mCameraDevice = null; + } + } + + /** + * Compares two {@code Size}s based on their areas. + */ + private static class CompareSizesByArea implements Comparator { + @Override + public int compare(Size lhs, Size rhs) { + // We cast here to ensure the multiplications won't overflow + return Long.signum((long) lhs.getWidth() * lhs.getHeight() - + (long) rhs.getWidth() * rhs.getHeight()); + } + + } + +} diff --git a/src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java b/src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java new file mode 100644 index 00000000000..3aed5241f9a --- /dev/null +++ b/src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 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.biometrics.face; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * Square layout that sets the height to be the same as width. + */ +public class FaceSquareFrameLayout extends FrameLayout { + + public FaceSquareFrameLayout(Context context) { + super(context); + } + + public FaceSquareFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FaceSquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public FaceSquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Don't call super, manually set their size below + int size = MeasureSpec.getSize(widthMeasureSpec); + + // Set this frame layout to be a square + setMeasuredDimension(size, size); + + // Set the children to be the same size (square) as well + final int numChildren = getChildCount(); + for (int i = 0; i < numChildren; i++) { + int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + this.getChildAt(i).measure(spec, spec); + } + } +} diff --git a/src/com/android/settings/biometrics/face/FaceSquareTextureView.java b/src/com/android/settings/biometrics/face/FaceSquareTextureView.java new file mode 100644 index 00000000000..ebbbc27cb75 --- /dev/null +++ b/src/com/android/settings/biometrics/face/FaceSquareTextureView.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 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.biometrics.face; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.TextureView; + +/** + * A square {@link TextureView}. + */ +public class FaceSquareTextureView extends TextureView { + + public FaceSquareTextureView(Context context) { + this(context, null); + } + + public FaceSquareTextureView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FaceSquareTextureView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + if (width < height) { + setMeasuredDimension(width, width); + } else { + setMeasuredDimension(height, height); + } + } +} From 06bf4e8aeb5860071627e6d6361799bec7e0c642 Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Thu, 9 Aug 2018 16:32:32 -0700 Subject: [PATCH 16/25] Use new SubscriptionInfo constructor Bug: 92796390 Test: build Change-Id: I5a90831b0050a0a201fce5b8f824fcb4c983a722 --- .../datausage/DataUsageSummaryPreferenceControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java index bd9db3daba9..6398361fb6e 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java @@ -355,7 +355,7 @@ public class DataUsageSummaryPreferenceControllerTest { mActivity, null, null, null); final SubscriptionInfo subInfo = new SubscriptionInfo(0, "123456", 0, "name", "carrier", - 0, 0, "number", 0, null, "123", "456", "ZX"); + 0, 0, "number", 0, null, "123", "456", "ZX", false, null, null); when(mSubscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(subInfo); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } From de74112f620c882233c3c1cc63ad44da00643d71 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 9 Aug 2018 16:34:31 -0700 Subject: [PATCH 17/25] More task affnity clean up Change-Id: I0df4f897b0e8649add5d4e8360ec3b9b95c214fd Fixes: 111213301 Test: manual --- AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0a5f24ba1be..7ee8f200b88 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1486,7 +1486,6 @@ From fee23c456a79b4d140b13bb6bcbf4ee263bf976c Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Mon, 6 Aug 2018 09:44:22 -0400 Subject: [PATCH 18/25] Follow slice API finalization Test: build Change-Id: I5671b180a949d5038f9a73caf84a6d266ef90cfa --- .../bluetooth/BluetoothSliceBuilder.java | 3 ++- .../flashlight/FlashlightSliceBuilder.java | 3 ++- .../location/LocationSliceBuilder.java | 3 ++- .../Enhanced4gLteSliceHelper.java | 3 ++- .../notification/ZenModeSliceBuilder.java | 3 ++- .../settings/slices/SliceBuilderUtils.java | 26 +++++++++++-------- .../settings/wifi/WifiSliceBuilder.java | 3 ++- .../wifi/calling/WifiCallingSliceHelper.java | 8 +++--- .../slices/SettingsSliceProviderTest.java | 9 +++++-- .../slices/SliceBuilderUtilsTest.java | 2 +- 10 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java index 7a569947b4f..3c2f1b8964f 100644 --- a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java +++ b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java @@ -37,6 +37,7 @@ import com.android.settings.slices.SliceBuilderUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; /** @@ -93,7 +94,7 @@ public class BluetoothSliceBuilder { return new ListBuilder(context, BLUETOOTH_URI, ListBuilder.INFINITY) .setAccentColor(color) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(title) .addEndItem(toggleSliceAction) .setPrimaryAction(primarySliceAction)) diff --git a/src/com/android/settings/flashlight/FlashlightSliceBuilder.java b/src/com/android/settings/flashlight/FlashlightSliceBuilder.java index a689fd23066..d4309ca66d0 100644 --- a/src/com/android/settings/flashlight/FlashlightSliceBuilder.java +++ b/src/com/android/settings/flashlight/FlashlightSliceBuilder.java @@ -42,6 +42,7 @@ import com.android.settings.slices.SliceBroadcastReceiver; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; @@ -93,7 +94,7 @@ public class FlashlightSliceBuilder { IconCompat.createWithResource(context, R.drawable.ic_signal_flashlight); return new ListBuilder(context, FLASHLIGHT_URI, ListBuilder.INFINITY) .setAccentColor(color) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(context.getText(R.string.power_flashlight)) .setTitleItem(icon, ICON_IMAGE) .setPrimaryAction( diff --git a/src/com/android/settings/location/LocationSliceBuilder.java b/src/com/android/settings/location/LocationSliceBuilder.java index cbdf7f0f709..4883ee8bbe8 100644 --- a/src/com/android/settings/location/LocationSliceBuilder.java +++ b/src/com/android/settings/location/LocationSliceBuilder.java @@ -37,6 +37,7 @@ import com.android.settings.slices.SliceBuilderUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; /** @@ -70,7 +71,7 @@ public class LocationSliceBuilder { return new ListBuilder(context, LOCATION_URI, ListBuilder.INFINITY) .setAccentColor(color) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(title) .setTitleItem(icon, ICON_IMAGE) .setPrimaryAction(primarySliceAction)) diff --git a/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelper.java b/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelper.java index f8f1447c979..960a082adf6 100644 --- a/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelper.java +++ b/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelper.java @@ -49,6 +49,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; /** @@ -183,7 +184,7 @@ public class Enhanced4gLteSliceHelper { return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) .setAccentColor(Utils.getColorAccentDefaultColor(mContext)) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(getEnhanced4glteModeTitle(subId)) .addEndItem( new SliceAction( diff --git a/src/com/android/settings/notification/ZenModeSliceBuilder.java b/src/com/android/settings/notification/ZenModeSliceBuilder.java index ad39d713055..dcdf6ef10eb 100644 --- a/src/com/android/settings/notification/ZenModeSliceBuilder.java +++ b/src/com/android/settings/notification/ZenModeSliceBuilder.java @@ -40,6 +40,7 @@ import com.android.settings.slices.SliceBuilderUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; public class ZenModeSliceBuilder { @@ -94,7 +95,7 @@ public class ZenModeSliceBuilder { return new ListBuilder(context, ZEN_MODE_URI, ListBuilder.INFINITY) .setAccentColor(color) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(title) .addEndItem(toggleSliceAction) .setPrimaryAction(primarySliceAction)) diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index c1c3b8e8229..b613ca4cb4a 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -29,6 +29,7 @@ import android.net.Uri; import android.os.Bundle; import android.provider.SettingsSlicesContract; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -47,12 +48,15 @@ import com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.InputRangeBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; @@ -250,11 +254,11 @@ public class SliceBuilderUtils { (TogglePreferenceController) controller; final SliceAction sliceAction = getToggleAction(context, sliceData, toggleController.isChecked()); - final List keywords = buildSliceKeywords(sliceData); + final Set keywords = buildSliceKeywords(sliceData); return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(rowBuilder -> rowBuilder + .addRow(new RowBuilder() .setTitle(sliceData.getTitle()) .setSubtitle(subtitleText) .setPrimaryAction( @@ -270,11 +274,11 @@ public class SliceBuilderUtils { final IconCompat icon = getSafeIcon(context, sliceData); final CharSequence subtitleText = getSubtitleText(context, controller, sliceData); @ColorInt final int color = Utils.getColorAccentDefaultColor(context); - final List keywords = buildSliceKeywords(sliceData); + final Set keywords = buildSliceKeywords(sliceData); return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(rowBuilder -> rowBuilder + .addRow(new RowBuilder() .setTitle(sliceData.getTitle()) .setSubtitle(subtitleText) .setPrimaryAction( @@ -293,11 +297,11 @@ public class SliceBuilderUtils { final CharSequence subtitleText = getSubtitleText(context, controller, sliceData); final SliceAction primaryAction = new SliceAction(contentIntent, icon, sliceData.getTitle()); - final List keywords = buildSliceKeywords(sliceData); + final Set keywords = buildSliceKeywords(sliceData); return new ListBuilder(context, sliceData.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addInputRange(builder -> builder + .addInputRange(new InputRangeBuilder() .setTitle(sliceData.getTitle()) .setSubtitle(subtitleText) .setPrimaryAction(primaryAction) @@ -343,8 +347,8 @@ public class SliceBuilderUtils { || TextUtils.equals(summary, doublePlaceHolder)); } - private static List buildSliceKeywords(SliceData data) { - final List keywords = new ArrayList<>(); + private static Set buildSliceKeywords(SliceData data) { + final Set keywords = new ArraySet<>(); keywords.add(data.getTitle()); @@ -366,7 +370,7 @@ public class SliceBuilderUtils { private static Slice buildUnavailableSlice(Context context, SliceData data) { final String title = data.getTitle(); - final List keywords = buildSliceKeywords(data); + final Set keywords = buildSliceKeywords(data); @ColorInt final int color = Utils.getColorAccentDefaultColor(context); final CharSequence summary = context.getText(R.string.disabled_dependent_setting_summary); final IconCompat icon = IconCompat.createWithResource(context, data.getIconResource()); @@ -375,9 +379,9 @@ public class SliceBuilderUtils { return new ListBuilder(context, data.getUri(), ListBuilder.INFINITY) .setAccentColor(color) - .addRow(builder -> builder + .addRow(new RowBuilder() .setTitle(title) - .setTitleItem(icon) + .setTitleItem(icon, ListBuilder.SMALL_IMAGE) .setSubtitle(summary) .setPrimaryAction(primaryAction)) .setKeywords(keywords) diff --git a/src/com/android/settings/wifi/WifiSliceBuilder.java b/src/com/android/settings/wifi/WifiSliceBuilder.java index a7bf16a5954..f6628a071fc 100644 --- a/src/com/android/settings/wifi/WifiSliceBuilder.java +++ b/src/com/android/settings/wifi/WifiSliceBuilder.java @@ -42,6 +42,7 @@ import com.android.settings.slices.SliceBuilderUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.RowBuilder; import androidx.slice.builders.SliceAction; /** @@ -95,7 +96,7 @@ public class WifiSliceBuilder { return new ListBuilder(context, WIFI_URI, ListBuilder.INFINITY) .setAccentColor(color) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(title) .setSubtitle(summary) .addEndItem(toggleSliceAction) diff --git a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java index f9014b70664..baf2a977267 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java +++ b/src/com/android/settings/wifi/calling/WifiCallingSliceHelper.java @@ -209,7 +209,7 @@ public class WifiCallingSliceHelper { return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) .setAccentColor(Utils.getColorAccentDefaultColor(mContext)) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(mContext.getText(R.string.wifi_calling_settings_title)) .addEndItem( new SliceAction( @@ -298,7 +298,7 @@ public class WifiCallingSliceHelper { // Top row shows information on current preference state ListBuilder listBuilder = new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) .setAccentColor(Utils.getColorAccentDefaultColor(mContext)); - listBuilder.setHeader(new ListBuilder.HeaderBuilder(listBuilder) + listBuilder.setHeader(new ListBuilder.HeaderBuilder() .setTitle(mContext.getText(R.string.wifi_calling_mode_title)) .setSubtitle(getWifiCallingPreferenceSummary(currentWfcPref)) .setPrimaryAction(new SliceAction( @@ -338,7 +338,7 @@ public class WifiCallingSliceHelper { int preferenceTitleResId, String action, boolean checked) { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.radio_button_check); - return new RowBuilder(listBuilder) + return new RowBuilder() .setTitle(mContext.getText(preferenceTitleResId)) .setTitleItem(new SliceAction(getBroadcastIntent(action), icon, mContext.getText(preferenceTitleResId), checked)); @@ -488,7 +488,7 @@ public class WifiCallingSliceHelper { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal); return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY) .setAccentColor(Utils.getColorAccentDefaultColor(mContext)) - .addRow(b -> b + .addRow(new RowBuilder() .setTitle(title) .setSubtitle(subtitle) .setPrimaryAction(new SliceAction( diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index ea2f2cac6e7..3cb502eee69 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -66,6 +66,9 @@ import java.util.List; import java.util.Set; import androidx.slice.Slice; +import androidx.slice.SliceProvider; +import androidx.slice.widget.SliceLiveData; +import com.android.settings.R; /** * TODO Investigate using ShadowContentResolver.registerProviderInternal(String, ContentProvider) @@ -81,7 +84,7 @@ public class SettingsSliceProviderTest { private static final String SUMMARY = "summary"; private static final String SCREEN_TITLE = "screen title"; private static final String FRAGMENT_NAME = "fragment name"; - private static final int ICON = 1234; // I declare a thumb war + private static final int ICON = R.drawable.ic_settings; private static final Uri URI = Uri.parse("content://com.android.settings.slices/test"); private static final String PREF_CONTROLLER = FakeToggleController.class.getName(); @@ -117,6 +120,8 @@ public class SettingsSliceProviderTest { mManager = mock(SliceManager.class); when(mContext.getSystemService(SliceManager.class)).thenReturn(mManager); when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList()); + + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); } @After @@ -480,7 +485,7 @@ public class SettingsSliceProviderTest { values.put(SlicesDatabaseHelper.IndexColumns.TITLE, TITLE); values.put(SlicesDatabaseHelper.IndexColumns.SUMMARY, "s"); values.put(SlicesDatabaseHelper.IndexColumns.SCREENTITLE, "s"); - values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, 1234); + values.put(SlicesDatabaseHelper.IndexColumns.ICON_RESOURCE, R.drawable.ic_settings); values.put(SlicesDatabaseHelper.IndexColumns.FRAGMENT, "test"); values.put(SlicesDatabaseHelper.IndexColumns.CONTROLLER, PREF_CONTROLLER); values.put(SlicesDatabaseHelper.IndexColumns.PLATFORM_SLICE, isPlatformSlice); diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index b96c12886d7..48489f9ba4b 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -64,7 +64,7 @@ public class SliceBuilderUtilsTest { private final String SCREEN_TITLE = "screen title"; private final String KEYWORDS = "a, b, c"; private final String FRAGMENT_NAME = "fragment name"; - private final int ICON = 1234; // I declare a thumb war + private final int ICON = R.drawable.ic_settings; private final Uri URI = Uri.parse("content://com.android.settings.slices/test"); private final Class TOGGLE_CONTROLLER = FakeToggleController.class; private final Class SLIDER_CONTROLLER = FakeSliderController.class; From 3541dcb040afa7cbd50258aa6efba0e307d557e1 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 9 Aug 2018 16:50:04 -0700 Subject: [PATCH 19/25] Let new homepage accept injected tiles Bug: 110405144 Test: robotests Change-Id: Idaa60fad23bab0989cccf687fb3ea47b7d56d957 --- .../dashboard/DashboardFragmentRegistry.java | 7 ++-- .../DashboardFragmentRegistryTest.java | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/dashboard/DashboardFragmentRegistryTest.java diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java index cd478fc43ad..4c371ddfdc3 100644 --- a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java +++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java @@ -30,6 +30,7 @@ import com.android.settings.deviceinfo.StorageDashboardFragment; import com.android.settings.display.NightDisplaySettings; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.gestures.GestureSettings; +import com.android.settings.homepage.TopLevelSettings; import com.android.settings.language.LanguageAndInputSettings; import com.android.settings.network.NetworkDashboardFragment; import com.android.settings.notification.ConfigureNotificationSettings; @@ -61,6 +62,8 @@ public class DashboardFragmentRegistry { static { PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>(); + PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(), + CategoryKey.CATEGORY_HOMEPAGE); PARENT_TO_CATEGORY_KEY_MAP.put( NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK); PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(), @@ -98,9 +101,9 @@ public class DashboardFragmentRegistry { PARENT_TO_CATEGORY_KEY_MAP.put(ZenModeSettings.class.getName(), CategoryKey.CATEGORY_DO_NOT_DISTURB); PARENT_TO_CATEGORY_KEY_MAP.put(GestureSettings.class.getName(), - CategoryKey.CATEGORY_GESTURES); + CategoryKey.CATEGORY_GESTURES); PARENT_TO_CATEGORY_KEY_MAP.put(NightDisplaySettings.class.getName(), - CategoryKey.CATEGORY_NIGHT_DISPLAY); + CategoryKey.CATEGORY_NIGHT_DISPLAY); CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size()); diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentRegistryTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentRegistryTest.java new file mode 100644 index 00000000000..1f68e2f04ec --- /dev/null +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentRegistryTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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.dashboard; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(SettingsRobolectricTestRunner.class) +public class DashboardFragmentRegistryTest { + @Test + public void pageAndKeyShouldHave1to1Mapping() { + assertThat(DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP.size()) + .isEqualTo(DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.size()); + } +} From e39e8d7f936b294aefbb0849fc22bf19d40fafa2 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Thu, 9 Aug 2018 17:13:01 -0700 Subject: [PATCH 20/25] Clean up obsolete battery settings files Fixes: 112440820 Test: robotest still pass Change-Id: I91e8a4094bb2077c7998ef663d5545e9a3e18d03 --- res/layout/battery_history_chart.xml | 42 - res/values/attrs.xml | 29 - res/values/dimens.xml | 2 - .../fuelgauge/BatteryHistoryChart.java | 1366 ----------------- .../fuelgauge/BatteryHistoryDetail.java | 126 -- .../settings/fuelgauge/PowerUsageBase.java | 2 - ...randfather_not_implementing_index_provider | 1 - 7 files changed, 1568 deletions(-) delete mode 100644 res/layout/battery_history_chart.xml delete mode 100644 src/com/android/settings/fuelgauge/BatteryHistoryChart.java delete mode 100644 src/com/android/settings/fuelgauge/BatteryHistoryDetail.java diff --git a/res/layout/battery_history_chart.xml b/res/layout/battery_history_chart.xml deleted file mode 100644 index c0c37f12951..00000000000 --- a/res/layout/battery_history_chart.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 7bdbe6dab14..b77c5442f7e 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -15,35 +15,6 @@ --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 234172827ba..024d212b9e0 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -33,8 +33,6 @@ 16dip 8dip - 120dp - 228dip 440dip 36sp diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java deleted file mode 100644 index e442a071bd1..00000000000 --- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java +++ /dev/null @@ -1,1366 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.fuelgauge; - -import android.content.Context; -import android.content.Intent; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.DashPathEffect; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Typeface; -import android.os.BatteryStats; -import android.os.BatteryStats.HistoryItem; -import android.os.SystemClock; -import android.telephony.ServiceState; -import android.text.TextPaint; -import android.text.format.DateFormat; -import android.text.format.Formatter; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TimeUtils; -import android.util.TypedValue; -import android.view.View; - -import com.android.settings.R; -import com.android.settings.Utils; - -import libcore.icu.LocaleData; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Locale; - -public class BatteryHistoryChart extends View { - static final boolean DEBUG = false; - static final String TAG = "BatteryHistoryChart"; - - static final int CHART_DATA_X_MASK = 0x0000ffff; - static final int CHART_DATA_BIN_MASK = 0xffff0000; - static final int CHART_DATA_BIN_SHIFT = 16; - - static class ChartData { - int[] mColors; - Paint[] mPaints; - - int mNumTicks; - int[] mTicks; - int mLastBin; - - void setColors(int[] colors) { - mColors = colors; - mPaints = new Paint[colors.length]; - for (int i=0; i 0) { - mTicks = new int[width*2]; - } else { - mTicks = null; - } - mNumTicks = 0; - mLastBin = 0; - } - - void addTick(int x, int bin) { - if (bin != mLastBin && mNumTicks < mTicks.length) { - mTicks[mNumTicks] = (x&CHART_DATA_X_MASK) | (bin<> CHART_DATA_BIN_SHIFT; - if (lastBin != 0) { - canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]); - } - lastBin = bin; - lastX = x; - } - - } - } - - static final int SANS = 1; - static final int SERIF = 2; - static final int MONOSPACE = 3; - - // First value if for phone off; first value is "scanning"; following values - // are battery stats signal strength buckets. - static final int NUM_PHONE_SIGNALS = 7; - - final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Paint mTimeRemainPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - final Paint mChargingPaint = new Paint(); - final Paint mScreenOnPaint = new Paint(); - final Paint mGpsOnPaint = new Paint(); - final Paint mFlashlightOnPaint = new Paint(); - final Paint mCameraOnPaint = new Paint(); - final Paint mWifiRunningPaint = new Paint(); - final Paint mCpuRunningPaint = new Paint(); - final Paint mDateLinePaint = new Paint(); - final ChartData mPhoneSignalChart = new ChartData(); - final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - final TextPaint mHeaderTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - final Paint mDebugRectPaint = new Paint(); - - final Path mBatLevelPath = new Path(); - final Path mBatGoodPath = new Path(); - final Path mBatWarnPath = new Path(); - final Path mBatCriticalPath = new Path(); - final Path mTimeRemainPath = new Path(); - final Path mChargingPath = new Path(); - final Path mScreenOnPath = new Path(); - final Path mGpsOnPath = new Path(); - final Path mFlashlightOnPath = new Path(); - final Path mCameraOnPath = new Path(); - final Path mWifiRunningPath = new Path(); - final Path mCpuRunningPath = new Path(); - final Path mDateLinePath = new Path(); - - BatteryStats mStats; - Intent mBatteryBroadcast; - long mStatsPeriod; - String mMaxPercentLabelString; - String mMinPercentLabelString; - String mDurationString; - String mChargeDurationString; - String mDrainString; - String mChargingLabel; - String mScreenOnLabel; - String mGpsOnLabel; - String mCameraOnLabel; - String mFlashlightOnLabel; - String mWifiRunningLabel; - String mCpuRunningLabel; - String mPhoneSignalLabel; - - BatteryInfo mInfo; - - int mChartMinHeight; - int mHeaderHeight; - - int mBatteryWarnLevel; - int mBatteryCriticalLevel; - - int mTextAscent; - int mTextDescent; - int mHeaderTextAscent; - int mHeaderTextDescent; - int mMaxPercentLabelStringWidth; - int mMinPercentLabelStringWidth; - int mDurationStringWidth; - int mChargeLabelStringWidth; - int mChargeDurationStringWidth; - int mDrainStringWidth; - - boolean mLargeMode; - - int mLastWidth = -1; - int mLastHeight = -1; - - int mLineWidth; - int mThinLineWidth; - int mChargingOffset; - int mScreenOnOffset; - int mGpsOnOffset; - int mFlashlightOnOffset; - int mCameraOnOffset; - int mWifiRunningOffset; - int mCpuRunningOffset; - int mPhoneSignalOffset; - int mLevelOffset; - int mLevelTop; - int mLevelBottom; - int mLevelLeft; - int mLevelRight; - - int mNumHist; - long mHistStart; - long mHistDataEnd; - long mHistEnd; - long mStartWallTime; - long mEndDataWallTime; - long mEndWallTime; - int mBatLow; - int mBatHigh; - boolean mHaveWifi; - boolean mHaveGps; - boolean mHavePhoneSignal; - boolean mHaveCamera; - boolean mHaveFlashlight; - - final ArrayList mTimeLabels = new ArrayList(); - final ArrayList mDateLabels = new ArrayList(); - - Bitmap mBitmap; - Canvas mCanvas; - - static class TextAttrs { - ColorStateList textColor = null; - int textSize = 15; - int typefaceIndex = -1; - int styleIndex = -1; - - void retrieve(Context context, TypedArray from, int index) { - TypedArray appearance = null; - int ap = from.getResourceId(index, -1); - if (ap != -1) { - appearance = context.obtainStyledAttributes(ap, - com.android.internal.R.styleable.TextAppearance); - } - if (appearance != null) { - int n = appearance.getIndexCount(); - for (int i = 0; i < n; i++) { - int attr = appearance.getIndex(i); - - switch (attr) { - case com.android.internal.R.styleable.TextAppearance_textColor: - textColor = appearance.getColorStateList(attr); - break; - - case com.android.internal.R.styleable.TextAppearance_textSize: - textSize = appearance.getDimensionPixelSize(attr, textSize); - break; - - case com.android.internal.R.styleable.TextAppearance_typeface: - typefaceIndex = appearance.getInt(attr, -1); - break; - - case com.android.internal.R.styleable.TextAppearance_textStyle: - styleIndex = appearance.getInt(attr, -1); - break; - } - } - - appearance.recycle(); - } - } - - void apply(Context context, TextPaint paint) { - paint.density = context.getResources().getDisplayMetrics().density; - paint.setCompatibilityScaling( - context.getResources().getCompatibilityInfo().applicationScale); - - paint.setColor(textColor.getDefaultColor()); - paint.setTextSize(textSize); - - Typeface tf = null; - switch (typefaceIndex) { - case SANS: - tf = Typeface.SANS_SERIF; - break; - - case SERIF: - tf = Typeface.SERIF; - break; - - case MONOSPACE: - tf = Typeface.MONOSPACE; - break; - } - - setTypeface(paint, tf, styleIndex); - } - - public void setTypeface(TextPaint paint, Typeface tf, int style) { - if (style > 0) { - if (tf == null) { - tf = Typeface.defaultFromStyle(style); - } else { - tf = Typeface.create(tf, style); - } - - paint.setTypeface(tf); - // now compute what (if any) algorithmic styling is needed - int typefaceStyle = tf != null ? tf.getStyle() : 0; - int need = style & ~typefaceStyle; - paint.setFakeBoldText((need & Typeface.BOLD) != 0); - paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); - } else { - paint.setFakeBoldText(false); - paint.setTextSkewX(0); - paint.setTypeface(tf); - } - } - } - - static class TimeLabel { - final int x; - final String label; - final int width; - - TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) { - this.x = x; - final String bestFormat = DateFormat.getBestDateTimePattern( - Locale.getDefault(), use24hr ? "km" : "ha"); - label = DateFormat.format(bestFormat, cal).toString(); - width = (int)paint.measureText(label); - } - } - - static class DateLabel { - final int x; - final String label; - final int width; - - DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) { - this.x = x; - final String bestFormat = DateFormat.getBestDateTimePattern( - Locale.getDefault(), dayFirst ? "dM" : "Md"); - label = DateFormat.format(bestFormat, cal).toString(); - width = (int)paint.measureText(label); - } - } - - public BatteryHistoryChart(Context context, AttributeSet attrs) { - super(context, attrs); - - if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!"); - - mBatteryWarnLevel = mContext.getResources().getInteger( - com.android.internal.R.integer.config_lowBatteryWarningLevel); - mBatteryCriticalLevel = mContext.getResources().getInteger( - com.android.internal.R.integer.config_criticalBatteryWarningLevel); - - mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 2, getResources().getDisplayMetrics()); - - int accentColor = Utils.getColorAccentDefaultColor(mContext); - mBatteryBackgroundPaint.setColor(accentColor); - mBatteryBackgroundPaint.setStyle(Paint.Style.FILL); - mBatteryGoodPaint.setARGB(128, 0, 128, 0); - mBatteryGoodPaint.setStyle(Paint.Style.STROKE); - mBatteryWarnPaint.setARGB(128, 128, 128, 0); - mBatteryWarnPaint.setStyle(Paint.Style.STROKE); - mBatteryCriticalPaint.setARGB(192, 128, 0, 0); - mBatteryCriticalPaint.setStyle(Paint.Style.STROKE); - mTimeRemainPaint.setColor(0xFFCED7BB); - mTimeRemainPaint.setStyle(Paint.Style.FILL); - mChargingPaint.setStyle(Paint.Style.STROKE); - mScreenOnPaint.setStyle(Paint.Style.STROKE); - mGpsOnPaint.setStyle(Paint.Style.STROKE); - mCameraOnPaint.setStyle(Paint.Style.STROKE); - mFlashlightOnPaint.setStyle(Paint.Style.STROKE); - mWifiRunningPaint.setStyle(Paint.Style.STROKE); - mCpuRunningPaint.setStyle(Paint.Style.STROKE); - mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS); - mDebugRectPaint.setARGB(255, 255, 0, 0); - mDebugRectPaint.setStyle(Paint.Style.STROKE); - mScreenOnPaint.setColor(accentColor); - mGpsOnPaint.setColor(accentColor); - mCameraOnPaint.setColor(accentColor); - mFlashlightOnPaint.setColor(accentColor); - mWifiRunningPaint.setColor(accentColor); - mCpuRunningPaint.setColor(accentColor); - mChargingPaint.setColor(accentColor); - - TypedArray a = - context.obtainStyledAttributes( - attrs, R.styleable.BatteryHistoryChart, 0, 0); - - final TextAttrs mainTextAttrs = new TextAttrs(); - final TextAttrs headTextAttrs = new TextAttrs(); - mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance); - headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance); - - int shadowcolor = 0; - float dx=0, dy=0, r=0; - - int n = a.getIndexCount(); - for (int i = 0; i < n; i++) { - int attr = a.getIndex(i); - - switch (attr) { - case R.styleable.BatteryHistoryChart_android_shadowColor: - shadowcolor = a.getInt(attr, 0); - break; - - case R.styleable.BatteryHistoryChart_android_shadowDx: - dx = a.getFloat(attr, 0); - break; - - case R.styleable.BatteryHistoryChart_android_shadowDy: - dy = a.getFloat(attr, 0); - break; - - case R.styleable.BatteryHistoryChart_android_shadowRadius: - r = a.getFloat(attr, 0); - break; - - case R.styleable.BatteryHistoryChart_android_textColor: - mainTextAttrs.textColor = a.getColorStateList(attr); - headTextAttrs.textColor = a.getColorStateList(attr); - break; - - case R.styleable.BatteryHistoryChart_android_textSize: - mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize); - headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize); - break; - - case R.styleable.BatteryHistoryChart_android_typeface: - mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex); - headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex); - break; - - case R.styleable.BatteryHistoryChart_android_textStyle: - mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex); - headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex); - break; - - case R.styleable.BatteryHistoryChart_barPrimaryColor: - mBatteryBackgroundPaint.setColor(a.getInt(attr, 0)); - mScreenOnPaint.setColor(a.getInt(attr, 0)); - mGpsOnPaint.setColor(a.getInt(attr, 0)); - mCameraOnPaint.setColor(a.getInt(attr, 0)); - mFlashlightOnPaint.setColor(a.getInt(attr, 0)); - mWifiRunningPaint.setColor(a.getInt(attr, 0)); - mCpuRunningPaint.setColor(a.getInt(attr, 0)); - mChargingPaint.setColor(a.getInt(attr, 0)); - break; - - case R.styleable.BatteryHistoryChart_barPredictionColor: - mTimeRemainPaint.setColor(a.getInt(attr, 0)); - break; - - case R.styleable.BatteryHistoryChart_chartMinHeight: - mChartMinHeight = a.getDimensionPixelSize(attr, 0); - break; - } - } - - a.recycle(); - - mainTextAttrs.apply(context, mTextPaint); - headTextAttrs.apply(context, mHeaderTextPaint); - - mDateLinePaint.set(mTextPaint); - mDateLinePaint.setStyle(Paint.Style.STROKE); - int hairlineWidth = mThinLineWidth/2; - if (hairlineWidth < 1) { - hairlineWidth = 1; - } - mDateLinePaint.setStrokeWidth(hairlineWidth); - mDateLinePaint.setPathEffect(new DashPathEffect(new float[] { - mThinLineWidth * 2, mThinLineWidth * 2 }, 0)); - - if (shadowcolor != 0) { - mTextPaint.setShadowLayer(r, dx, dy, shadowcolor); - mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor); - } - } - - void setStats(BatteryStats stats, Intent broadcast) { - mStats = stats; - mBatteryBroadcast = broadcast; - - if (DEBUG) Log.d(TAG, "Setting stats..."); - - final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; - - long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs, - BatteryStats.STATS_SINCE_CHARGED); - mStatsPeriod = uSecTime; - mChargingLabel = getContext().getString(R.string.battery_stats_charging_label); - mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label); - mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label); - mCameraOnLabel = getContext().getString(R.string.battery_stats_camera_on_label); - mFlashlightOnLabel = getContext().getString(R.string.battery_stats_flashlight_on_label); - mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label); - mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label); - mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label); - - mMaxPercentLabelString = Utils.formatPercentage(100); - mMinPercentLabelString = Utils.formatPercentage(0); - BatteryInfo.getBatteryInfo(getContext(), info -> { - mInfo = info; - mDrainString = ""; - mChargeDurationString = ""; - setContentDescription(mInfo.chargeLabel); - - int pos = 0; - int lastInteresting = 0; - byte lastLevel = -1; - mBatLow = 0; - mBatHigh = 100; - mStartWallTime = 0; - mEndDataWallTime = 0; - mEndWallTime = 0; - mHistStart = 0; - mHistEnd = 0; - long lastWallTime = 0; - long lastRealtime = 0; - int aggrStates = 0; - int aggrStates2 = 0; - boolean first = true; - if (stats.startIteratingHistoryLocked()) { - final HistoryItem rec = new HistoryItem(); - while (stats.getNextHistoryLocked(rec)) { - pos++; - if (first) { - first = false; - mHistStart = rec.time; - } - if (rec.cmd == HistoryItem.CMD_CURRENT_TIME - || rec.cmd == HistoryItem.CMD_RESET) { - // If there is a ridiculously large jump in time, then we won't be - // able to create a good chart with that data, so just ignore the - // times we got before and pretend like our data extends back from - // the time we have now. - // Also, if we are getting a time change and we are less than 5 minutes - // since the start of the history real time, then also use this new - // time to compute the base time, since whatever time we had before is - // pretty much just noise. - if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L)) - || rec.time < (mHistStart+(5*60*1000L))) { - mStartWallTime = 0; - } - lastWallTime = rec.currentTime; - lastRealtime = rec.time; - if (mStartWallTime == 0) { - mStartWallTime = lastWallTime - (lastRealtime-mHistStart); - } - } - if (rec.isDeltaData()) { - if (rec.batteryLevel != lastLevel || pos == 1) { - lastLevel = rec.batteryLevel; - } - lastInteresting = pos; - mHistDataEnd = rec.time; - aggrStates |= rec.states; - aggrStates2 |= rec.states2; - } - } - } - mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000); - mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime; - mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000); - mNumHist = lastInteresting; - mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0; - mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0; - mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0; - mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0 - || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG - |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG - |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0; - if (!com.android.settingslib.Utils.isWifiOnly(getContext())) { - mHavePhoneSignal = true; - } - if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1; - }, mStats, false /* shortString */); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString); - mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString); - mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString); - mChargeLabelStringWidth = (int) mHeaderTextPaint.measureText( - mInfo.chargeLabel.toString()); - mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString); - mTextAscent = (int)mTextPaint.ascent(); - mTextDescent = (int)mTextPaint.descent(); - mHeaderTextAscent = (int)mHeaderTextPaint.ascent(); - mHeaderTextDescent = (int)mHeaderTextPaint.descent(); - int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent; - mHeaderHeight = headerTextHeight*2 - mTextAscent; - setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), - getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec)); - } - - void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath, - int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn, - boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning, - boolean lastCpuRunning, Path lastPath) { - if (curLevelPath != null) { - if (lastX >= 0 && lastX < w) { - if (lastPath != null) { - lastPath.lineTo(w, y); - } - curLevelPath.lineTo(w, y); - } - curLevelPath.lineTo(w, mLevelTop+levelh); - curLevelPath.lineTo(startX, mLevelTop+levelh); - curLevelPath.close(); - } - - if (lastCharging) { - mChargingPath.lineTo(w, h-mChargingOffset); - } - if (lastScreenOn) { - mScreenOnPath.lineTo(w, h-mScreenOnOffset); - } - if (lastGpsOn) { - mGpsOnPath.lineTo(w, h-mGpsOnOffset); - } - if (lastFlashlightOn) { - mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset); - } - if (lastCameraOn) { - mCameraOnPath.lineTo(w, h-mCameraOnOffset); - } - if (lastWifiRunning) { - mWifiRunningPath.lineTo(w, h-mWifiRunningOffset); - } - if (lastCpuRunning) { - mCpuRunningPath.lineTo(w, h - mCpuRunningOffset); - } - if (mHavePhoneSignal) { - mPhoneSignalChart.finish(w); - } - } - - private boolean is24Hour() { - return DateFormat.is24HourFormat(getContext()); - } - - private boolean isDayFirst() { - final String value = LocaleData.get(getResources().getConfiguration().locale) - .getDateFormat(java.text.DateFormat.SHORT); - return value.indexOf('M') > value.indexOf('d'); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h); - - if (mLastWidth == w && mLastHeight == h) { - return; - } - - if (mLastWidth == 0 || mLastHeight == 0) { - return; - } - - if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h); - - mLastWidth = w; - mLastHeight = h; - mBitmap = null; - mCanvas = null; - - int textHeight = mTextDescent - mTextAscent; - if (h > ((textHeight*10)+mChartMinHeight)) { - mLargeMode = true; - if (h > (textHeight*15)) { - // Plenty of room for the chart. - mLineWidth = textHeight/2; - } else { - // Compress lines to make more room for chart. - mLineWidth = textHeight/3; - } - } else { - mLargeMode = false; - mLineWidth = mThinLineWidth; - } - if (mLineWidth <= 0) mLineWidth = 1; - - mLevelTop = mHeaderHeight; - mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3; - mLevelRight = w; - int levelWidth = mLevelRight-mLevelLeft; - - mTextPaint.setStrokeWidth(mThinLineWidth); - mBatteryGoodPaint.setStrokeWidth(mThinLineWidth); - mBatteryWarnPaint.setStrokeWidth(mThinLineWidth); - mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth); - mChargingPaint.setStrokeWidth(mLineWidth); - mScreenOnPaint.setStrokeWidth(mLineWidth); - mGpsOnPaint.setStrokeWidth(mLineWidth); - mCameraOnPaint.setStrokeWidth(mLineWidth); - mFlashlightOnPaint.setStrokeWidth(mLineWidth); - mWifiRunningPaint.setStrokeWidth(mLineWidth); - mCpuRunningPaint.setStrokeWidth(mLineWidth); - mDebugRectPaint.setStrokeWidth(1); - - int fullBarOffset = textHeight + mLineWidth; - - if (mLargeMode) { - mChargingOffset = mLineWidth; - mScreenOnOffset = mChargingOffset + fullBarOffset; - mCpuRunningOffset = mScreenOnOffset + fullBarOffset; - mWifiRunningOffset = mCpuRunningOffset + fullBarOffset; - mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0); - mFlashlightOnOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0); - mCameraOnOffset = mFlashlightOnOffset + (mHaveFlashlight ? fullBarOffset : 0); - mPhoneSignalOffset = mCameraOnOffset + (mHaveCamera ? fullBarOffset : 0); - mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0) - + mLineWidth*2 + mLineWidth/2; - if (mHavePhoneSignal) { - mPhoneSignalChart.init(w); - } - } else { - mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset = - mWifiRunningOffset = mCpuRunningOffset = mChargingOffset = - mPhoneSignalOffset = 0; - mLevelOffset = fullBarOffset + mThinLineWidth*4; - if (mHavePhoneSignal) { - mPhoneSignalChart.init(0); - } - } - - mBatLevelPath.reset(); - mBatGoodPath.reset(); - mBatWarnPath.reset(); - mTimeRemainPath.reset(); - mBatCriticalPath.reset(); - mScreenOnPath.reset(); - mGpsOnPath.reset(); - mFlashlightOnPath.reset(); - mCameraOnPath.reset(); - mWifiRunningPath.reset(); - mCpuRunningPath.reset(); - mChargingPath.reset(); - - mTimeLabels.clear(); - mDateLabels.clear(); - - final long walltimeStart = mStartWallTime; - final long walltimeChange = mEndWallTime > walltimeStart - ? (mEndWallTime-walltimeStart) : 1; - long curWalltime = mStartWallTime; - long lastRealtime = 0; - - final int batLow = mBatLow; - final int batChange = mBatHigh-mBatLow; - - final int levelh = h - mLevelOffset - mLevelTop; - mLevelBottom = mLevelTop + levelh; - - int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1; - int i = 0; - Path curLevelPath = null; - Path lastLinePath = null; - boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false; - boolean lastFlashlightOn = false, lastCameraOn = false; - boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false; - int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; - final int N = mNumHist; - if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) { - final HistoryItem rec = new HistoryItem(); - while (mStats.getNextHistoryLocked(rec) && i < N) { - if (rec.isDeltaData()) { - curWalltime += rec.time-lastRealtime; - lastRealtime = rec.time; - x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange); - if (x < 0) { - x = 0; - } - if (false) { - StringBuilder sb = new StringBuilder(128); - sb.append("walloff="); - TimeUtils.formatDuration(curWalltime - walltimeStart, sb); - sb.append(" wallchange="); - TimeUtils.formatDuration(walltimeChange, sb); - sb.append(" x="); - sb.append(x); - Log.d("foo", sb.toString()); - } - y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange; - - if (lastX != x) { - // We have moved by at least a pixel. - if (lastY != y) { - // Don't plot changes within a pixel. - Path path; - byte value = rec.batteryLevel; - if (value <= mBatteryCriticalLevel) path = mBatCriticalPath; - else if (value <= mBatteryWarnLevel) path = mBatWarnPath; - else path = null; //mBatGoodPath; - - if (path != lastLinePath) { - if (lastLinePath != null) { - lastLinePath.lineTo(x, y); - } - if (path != null) { - path.moveTo(x, y); - } - lastLinePath = path; - } else if (path != null) { - path.lineTo(x, y); - } - - if (curLevelPath == null) { - curLevelPath = mBatLevelPath; - curLevelPath.moveTo(x, y); - startX = x; - } else { - curLevelPath.lineTo(x, y); - } - lastX = x; - lastY = y; - } - } - - if (mLargeMode) { - final boolean charging = - (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0; - if (charging != lastCharging) { - if (charging) { - mChargingPath.moveTo(x, h-mChargingOffset); - } else { - mChargingPath.lineTo(x, h-mChargingOffset); - } - lastCharging = charging; - } - - final boolean screenOn = - (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0; - if (screenOn != lastScreenOn) { - if (screenOn) { - mScreenOnPath.moveTo(x, h-mScreenOnOffset); - } else { - mScreenOnPath.lineTo(x, h-mScreenOnOffset); - } - lastScreenOn = screenOn; - } - - final boolean gpsOn = - (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0; - if (gpsOn != lastGpsOn) { - if (gpsOn) { - mGpsOnPath.moveTo(x, h-mGpsOnOffset); - } else { - mGpsOnPath.lineTo(x, h-mGpsOnOffset); - } - lastGpsOn = gpsOn; - } - - final boolean flashlightOn = - (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0; - if (flashlightOn != lastFlashlightOn) { - if (flashlightOn) { - mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset); - } else { - mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset); - } - lastFlashlightOn = flashlightOn; - } - - final boolean cameraOn = - (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0; - if (cameraOn != lastCameraOn) { - if (cameraOn) { - mCameraOnPath.moveTo(x, h-mCameraOnOffset); - } else { - mCameraOnPath.lineTo(x, h-mCameraOnOffset); - } - lastCameraOn = cameraOn; - } - - final int wifiSupplState = - ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK) - >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT); - boolean wifiRunning; - if (lastWifiSupplState != wifiSupplState) { - lastWifiSupplState = wifiSupplState; - switch (wifiSupplState) { - case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED: - case BatteryStats.WIFI_SUPPL_STATE_DORMANT: - case BatteryStats.WIFI_SUPPL_STATE_INACTIVE: - case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED: - case BatteryStats.WIFI_SUPPL_STATE_INVALID: - case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED: - wifiRunning = lastWifiSupplRunning = false; - break; - default: - wifiRunning = lastWifiSupplRunning = true; - break; - } - } else { - wifiRunning = lastWifiSupplRunning; - } - if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG - |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG - |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) { - wifiRunning = true; - } - if (wifiRunning != lastWifiRunning) { - if (wifiRunning) { - mWifiRunningPath.moveTo(x, h-mWifiRunningOffset); - } else { - mWifiRunningPath.lineTo(x, h-mWifiRunningOffset); - } - lastWifiRunning = wifiRunning; - } - - final boolean cpuRunning = - (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0; - if (cpuRunning != lastCpuRunning) { - if (cpuRunning) { - mCpuRunningPath.moveTo(x, h - mCpuRunningOffset); - } else { - mCpuRunningPath.lineTo(x, h - mCpuRunningOffset); - } - lastCpuRunning = cpuRunning; - } - - if (mLargeMode && mHavePhoneSignal) { - int bin; - if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK) - >> HistoryItem.STATE_PHONE_STATE_SHIFT) - == ServiceState.STATE_POWER_OFF) { - bin = 0; - } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) { - bin = 1; - } else { - bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK) - >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT; - bin += 2; - } - mPhoneSignalChart.addTick(x, bin); - } - } - - } else { - long lastWalltime = curWalltime; - if (rec.cmd == HistoryItem.CMD_CURRENT_TIME - || rec.cmd == HistoryItem.CMD_RESET) { - if (rec.currentTime >= mStartWallTime) { - curWalltime = rec.currentTime; - } else { - curWalltime = mStartWallTime + (rec.time-mHistStart); - } - lastRealtime = rec.time; - } - - if (rec.cmd != HistoryItem.CMD_OVERFLOW - && (rec.cmd != HistoryItem.CMD_CURRENT_TIME - || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) { - if (curLevelPath != null) { - finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX, - lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, - lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath); - lastX = lastY = -1; - curLevelPath = null; - lastLinePath = null; - lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn = - lastCameraOn = lastCpuRunning = false; - } - } - } - - i++; - } - mStats.finishIteratingHistoryLocked(); - } - - if (lastY < 0 || lastX < 0) { - // Didn't get any data... - x = lastX = mLevelLeft; - y = lastY = mLevelTop + levelh - ((mInfo.batteryLevel -batLow)*(levelh-1))/batChange; - Path path; - byte value = (byte)mInfo.batteryLevel; - if (value <= mBatteryCriticalLevel) path = mBatCriticalPath; - else if (value <= mBatteryWarnLevel) path = mBatWarnPath; - else path = null; //mBatGoodPath; - if (path != null) { - path.moveTo(x, y); - lastLinePath = path; - } - mBatLevelPath.moveTo(x, y); - curLevelPath = mBatLevelPath; - x = w; - } else { - // Figure out where the actual data ends on the screen. - x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange); - if (x < 0) { - x = 0; - } - } - - finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX, - lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn, - lastWifiRunning, lastCpuRunning, lastLinePath); - - if (x < w) { - // If we reserved room for the remaining time, create a final path to draw - // that part of the UI. - mTimeRemainPath.moveTo(x, lastY); - int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange; - int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange; - if (mInfo.discharging) { - mTimeRemainPath.lineTo(mLevelRight, emptyY); - } else { - mTimeRemainPath.lineTo(mLevelRight, fullY); - mTimeRemainPath.lineTo(mLevelRight, emptyY); - } - mTimeRemainPath.lineTo(x, emptyY); - mTimeRemainPath.close(); - } - - if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) { - // Create the time labels at the bottom. - boolean is24hr = is24Hour(); - Calendar calStart = Calendar.getInstance(); - calStart.setTimeInMillis(mStartWallTime); - calStart.set(Calendar.MILLISECOND, 0); - calStart.set(Calendar.SECOND, 0); - calStart.set(Calendar.MINUTE, 0); - long startRoundTime = calStart.getTimeInMillis(); - if (startRoundTime < mStartWallTime) { - calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1); - startRoundTime = calStart.getTimeInMillis(); - } - Calendar calEnd = Calendar.getInstance(); - calEnd.setTimeInMillis(mEndWallTime); - calEnd.set(Calendar.MILLISECOND, 0); - calEnd.set(Calendar.SECOND, 0); - calEnd.set(Calendar.MINUTE, 0); - long endRoundTime = calEnd.getTimeInMillis(); - if (startRoundTime < endRoundTime) { - addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr); - Calendar calMid = Calendar.getInstance(); - calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2)); - calMid.set(Calendar.MILLISECOND, 0); - calMid.set(Calendar.SECOND, 0); - calMid.set(Calendar.MINUTE, 0); - long calMidMillis = calMid.getTimeInMillis(); - if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) { - addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr); - } - addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr); - } - - // Create the date labels if the chart includes multiple days - if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) || - calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) { - boolean isDayFirst = isDayFirst(); - calStart.set(Calendar.HOUR_OF_DAY, 0); - startRoundTime = calStart.getTimeInMillis(); - if (startRoundTime < mStartWallTime) { - calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1); - startRoundTime = calStart.getTimeInMillis(); - } - calEnd.set(Calendar.HOUR_OF_DAY, 0); - endRoundTime = calEnd.getTimeInMillis(); - if (startRoundTime < endRoundTime) { - addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst); - Calendar calMid = Calendar.getInstance(); - - // The middle between two beginnings of days can be anywhere between -1 to 13 - // after the beginning of the "median" day. - calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2) - + 2 * 60 * 60 * 1000); - calMid.set(Calendar.HOUR_OF_DAY, 0); - calMid.set(Calendar.MINUTE, 0); - long calMidMillis = calMid.getTimeInMillis(); - if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) { - addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst); - } - } - addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst); - } - } - - if (mTimeLabels.size() < 2) { - // If there are fewer than 2 time labels, then they are useless. Just - // show an axis label giving the entire duration. - mDurationString = Formatter.formatShortElapsedTime(getContext(), - mEndWallTime - mStartWallTime); - mDurationStringWidth = (int)mTextPaint.measureText(mDurationString); - } else { - mDurationString = null; - mDurationStringWidth = 0; - } - } - - void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) { - final long walltimeStart = mStartWallTime; - final long walltimeChange = mEndWallTime-walltimeStart; - mTimeLabels.add(new TimeLabel(mTextPaint, - levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft)) - / walltimeChange), - cal, is24hr)); - } - - void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) { - final long walltimeStart = mStartWallTime; - final long walltimeChange = mEndWallTime-walltimeStart; - mDateLabels.add(new DateLabel(mTextPaint, - levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft)) - / walltimeChange), - cal, isDayFirst)); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - final int width = getWidth(); - final int height = getHeight(); - - //buildBitmap(width, height); - - if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height); - //canvas.drawBitmap(mBitmap, 0, 0, null); - drawChart(canvas, width, height); - } - - void buildBitmap(int width, int height) { - if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) { - return; - } - - if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height); - - mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height, - Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBitmap); - drawChart(mCanvas, width, height); - } - - void drawChart(Canvas canvas, int width, int height) { - final boolean layoutRtl = isLayoutRtl(); - final int textStartX = layoutRtl ? width : 0; - final int textEndX = layoutRtl ? 0 : width; - final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT; - final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT; - - if (DEBUG) { - canvas.drawRect(1, 1, width, height, mDebugRectPaint); - } - - if (DEBUG) Log.d(TAG, "Drawing level path."); - canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint); - if (!mTimeRemainPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing time remain path."); - canvas.drawPath(mTimeRemainPath, mTimeRemainPaint); - } - if (mTimeLabels.size() > 1) { - int y = mLevelBottom - mTextAscent + (mThinLineWidth*4); - int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2); - mTextPaint.setTextAlign(Paint.Align.LEFT); - int lastX = 0; - for (int i=0; i (width-nextLabel.width-mTextAscent)) { - continue; - } - if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x); - canvas.drawText(label.label, x, y, mTextPaint); - canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint); - lastX = x + label.width; - } else { - int x = label.x - label.width/2; - if ((x+label.width) >= width) { - x = width-1-label.width; - } - if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x); - canvas.drawText(label.label, x, y, mTextPaint); - canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint); - } - } - } else if (mDurationString != null) { - int y = mLevelBottom - mTextAscent + (mThinLineWidth*4); - mTextPaint.setTextAlign(Paint.Align.LEFT); - canvas.drawText(mDurationString, - mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2, - y, mTextPaint); - } - - int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3; - mHeaderTextPaint.setTextAlign(textAlignLeft); - if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mInfo.chargeLabel); - canvas.drawText(mInfo.chargeLabel.toString(), textStartX, headerTop, - mHeaderTextPaint); - int stringHalfWidth = mChargeDurationStringWidth / 2; - if (layoutRtl) stringHalfWidth = -stringHalfWidth; - int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2) - + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth); - if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString); - canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop, - mHeaderTextPaint); - mHeaderTextPaint.setTextAlign(textAlignRight); - if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString); - canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint); - - if (!mBatGoodPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing good battery path"); - canvas.drawPath(mBatGoodPath, mBatteryGoodPaint); - } - if (!mBatWarnPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing warn battery path"); - canvas.drawPath(mBatWarnPath, mBatteryWarnPaint); - } - if (!mBatCriticalPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing critical battery path"); - canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint); - } - if (mHavePhoneSignal) { - if (DEBUG) Log.d(TAG, "Drawing phone signal path"); - int top = height-mPhoneSignalOffset - (mLineWidth/2); - mPhoneSignalChart.draw(canvas, top, mLineWidth); - } - if (!mScreenOnPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing screen on path"); - canvas.drawPath(mScreenOnPath, mScreenOnPaint); - } - if (!mChargingPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing charging path"); - canvas.drawPath(mChargingPath, mChargingPaint); - } - if (mHaveGps) { - if (!mGpsOnPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing gps path"); - canvas.drawPath(mGpsOnPath, mGpsOnPaint); - } - } - if (mHaveFlashlight) { - if (!mFlashlightOnPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing flashlight path"); - canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint); - } - } - if (mHaveCamera) { - if (!mCameraOnPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing camera path"); - canvas.drawPath(mCameraOnPath, mCameraOnPaint); - } - } - if (mHaveWifi) { - if (!mWifiRunningPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing wifi path"); - canvas.drawPath(mWifiRunningPath, mWifiRunningPaint); - } - } - if (!mCpuRunningPath.isEmpty()) { - if (DEBUG) Log.d(TAG, "Drawing running path"); - canvas.drawPath(mCpuRunningPath, mCpuRunningPaint); - } - - if (mLargeMode) { - if (DEBUG) Log.d(TAG, "Drawing large mode labels"); - Paint.Align align = mTextPaint.getTextAlign(); - mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start - if (mHavePhoneSignal) { - canvas.drawText(mPhoneSignalLabel, textStartX, - height - mPhoneSignalOffset - mTextDescent, mTextPaint); - } - if (mHaveGps) { - canvas.drawText(mGpsOnLabel, textStartX, - height - mGpsOnOffset - mTextDescent, mTextPaint); - } - if (mHaveFlashlight) { - canvas.drawText(mFlashlightOnLabel, textStartX, - height - mFlashlightOnOffset - mTextDescent, mTextPaint); - } - if (mHaveCamera) { - canvas.drawText(mCameraOnLabel, textStartX, - height - mCameraOnOffset - mTextDescent, mTextPaint); - } - if (mHaveWifi) { - canvas.drawText(mWifiRunningLabel, textStartX, - height - mWifiRunningOffset - mTextDescent, mTextPaint); - } - canvas.drawText(mCpuRunningLabel, textStartX, - height - mCpuRunningOffset - mTextDescent, mTextPaint); - canvas.drawText(mChargingLabel, textStartX, - height - mChargingOffset - mTextDescent, mTextPaint); - canvas.drawText(mScreenOnLabel, textStartX, - height - mScreenOnOffset - mTextDescent, mTextPaint); - mTextPaint.setTextAlign(align); - } - - canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth, - mLevelBottom+(mThinLineWidth/2), mTextPaint); - if (mLargeMode) { - for (int i=0; i<10; i++) { - int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10; - canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y, - mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint); - } - } - if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth - + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString)); - canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint); - canvas.drawText(mMinPercentLabelString, - mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth, - mLevelBottom - mThinLineWidth, mTextPaint); - canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width, - mLevelBottom+mThinLineWidth, mTextPaint); - - if (mDateLabels.size() > 0) { - int ytop = mLevelTop + mTextAscent; - int ybottom = mLevelBottom; - int lastLeft = mLevelRight; - mTextPaint.setTextAlign(Paint.Align.LEFT); - for (int i=mDateLabels.size()-1; i>=0; i--) { - DateLabel label = mDateLabels.get(i); - int left = label.x - mThinLineWidth; - int x = label.x + mThinLineWidth*2; - if ((x+label.width) >= lastLeft) { - x = label.x - mThinLineWidth*2 - label.width; - left = x - mThinLineWidth; - if (left >= lastLeft) { - // okay we give up. - continue; - } - } - if (left < mLevelLeft) { - // Won't fit on left, give up. - continue; - } - mDateLinePath.reset(); - mDateLinePath.moveTo(label.x, ytop); - mDateLinePath.lineTo(label.x, ybottom); - canvas.drawPath(mDateLinePath, mDateLinePaint); - canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint); - } - } - } -} diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java deleted file mode 100644 index 1343fefca60..00000000000 --- a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.fuelgauge; - -import android.content.Intent; -import android.os.BatteryStats; -import android.os.BatteryStats.HistoryItem; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.os.BatteryStatsHelper; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.fuelgauge.BatteryActiveView.BatteryActiveProvider; -import com.android.settings.widget.UsageView; - -public class BatteryHistoryDetail extends SettingsPreferenceFragment { - public static final String EXTRA_STATS = "stats"; - public static final String EXTRA_BROADCAST = "broadcast"; - public static final String BATTERY_HISTORY_FILE = "tmp_bat_history.bin"; - - private BatteryStats mStats; - private Intent mBatteryBroadcast; - - private BatteryFlagParser mChargingParser; - private BatteryFlagParser mScreenOn; - private BatteryFlagParser mGpsParser; - private BatteryFlagParser mFlashlightParser; - private BatteryFlagParser mCameraParser; - private BatteryWifiParser mWifiParser; - private BatteryFlagParser mCpuParser; - private BatteryCellParser mPhoneParser; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - String histFile = getArguments().getString(EXTRA_STATS); - mStats = BatteryStatsHelper.statsFromFile(getActivity(), histFile); - mBatteryBroadcast = getArguments().getParcelable(EXTRA_BROADCAST); - - TypedValue value = new TypedValue(); - getContext().getTheme().resolveAttribute(android.R.attr.colorAccent, value, true); - int accentColor = getContext().getColor(value.resourceId); - - mChargingParser = new BatteryFlagParser(accentColor, false, - HistoryItem.STATE_BATTERY_PLUGGED_FLAG); - mScreenOn = new BatteryFlagParser(accentColor, false, - HistoryItem.STATE_SCREEN_ON_FLAG); - mGpsParser = new BatteryFlagParser(accentColor, false, - HistoryItem.STATE_GPS_ON_FLAG); - mFlashlightParser = new BatteryFlagParser(accentColor, true, - HistoryItem.STATE2_FLASHLIGHT_FLAG); - mCameraParser = new BatteryFlagParser(accentColor, true, - HistoryItem.STATE2_CAMERA_FLAG); - mWifiParser = new BatteryWifiParser(accentColor); - mCpuParser = new BatteryFlagParser(accentColor, false, - HistoryItem.STATE_CPU_RUNNING_FLAG); - mPhoneParser = new BatteryCellParser(); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.battery_history_detail, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - updateEverything(); - } - - private void updateEverything() { - BatteryInfo.getBatteryInfo(getContext(), info -> { - final View view = getView(); - info.bindHistory((UsageView) view.findViewById(R.id.battery_usage), mChargingParser, - mScreenOn, mGpsParser, mFlashlightParser, mCameraParser, mWifiParser, - mCpuParser, mPhoneParser); - ((TextView) view.findViewById(R.id.charge)).setText(info.batteryPercentString); - ((TextView) view.findViewById(R.id.estimation)).setText(info.remainingLabel); - - bindData(mChargingParser, R.string.battery_stats_charging_label, R.id.charging_group); - bindData(mScreenOn, R.string.battery_stats_screen_on_label, R.id.screen_on_group); - bindData(mGpsParser, R.string.battery_stats_gps_on_label, R.id.gps_group); - bindData(mFlashlightParser, R.string.battery_stats_flashlight_on_label, - R.id.flashlight_group); - bindData(mCameraParser, R.string.battery_stats_camera_on_label, R.id.camera_group); - bindData(mWifiParser, R.string.battery_stats_wifi_running_label, R.id.wifi_group); - bindData(mCpuParser, R.string.battery_stats_wake_lock_label, R.id.cpu_group); - bindData(mPhoneParser, R.string.battery_stats_phone_signal_label, - R.id.cell_network_group); - }, mStats, false /* shortString */); - } - - private void bindData(BatteryActiveProvider provider, int label, int groupId) { - View group = getView().findViewById(groupId); - group.setVisibility(provider.hasData() ? View.VISIBLE : View.GONE); - ((TextView) group.findViewById(android.R.id.title)).setText(label); - ((BatteryActiveView) group.findViewById(R.id.battery_active)).setProvider(provider); - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.FUELGAUGE_BATTERY_HISTORY_DETAIL; - } -} diff --git a/src/com/android/settings/fuelgauge/PowerUsageBase.java b/src/com/android/settings/fuelgauge/PowerUsageBase.java index 06200a3cc3c..a11270befb7 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageBase.java +++ b/src/com/android/settings/fuelgauge/PowerUsageBase.java @@ -72,8 +72,6 @@ public abstract class PowerUsageBase extends DashboardFragment { @Override public void onResume() { super.onResume(); - - BatteryStatsHelper.dropFile(getActivity(), BatteryHistoryDetail.BATTERY_HISTORY_FILE); mBatteryBroadcastReceiver.register(); } diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 37869e97cf2..1a7b28958a5 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -39,7 +39,6 @@ com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionMi com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages com.android.settings.enterprise.EnterpriseSetDefaultAppsListFragment com.android.settings.fuelgauge.AdvancedPowerUsageDetail -com.android.settings.fuelgauge.BatteryHistoryDetail com.android.settings.fuelgauge.InactiveApps com.android.settings.fuelgauge.RestrictedAppDetails com.android.settings.IccLockSettings From 195f61b2c7adb026b5a4f8cca356f991c6ca586c Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Tue, 24 Jul 2018 18:15:55 +0800 Subject: [PATCH 21/25] Added the CardContentProvider - Added the CardContentProvider - Added the CardDatabaseHelper - Added the CardContentProviderTest, CardDatabaseHelperTest - Modified CardDatabaseHelper and added the locale and expire_time_ms - Added the permission for CardContentProvider - Modified CardDatabaseHelper and added the category and availability_uri - Added the UriMatcher Test: robotest Bug: 111820446 Change-Id: Ie9df065133307f4eac2680637f67be1dcb8310a3 --- AndroidManifest.xml | 6 + .../homepage/CardContentProvider.java | 166 +++++++++++++++ .../settings/homepage/CardDatabaseHelper.java | 190 ++++++++++++++++++ .../homepage/CardContentProviderTest.java | 147 ++++++++++++++ .../homepage/CardDatabaseHelperTest.java | 83 ++++++++ 5 files changed, 592 insertions(+) create mode 100644 src/com/android/settings/homepage/CardContentProvider.java create mode 100644 src/com/android/settings/homepage/CardDatabaseHelper.java create mode 100644 tests/robotests/src/com/android/settings/homepage/CardContentProviderTest.java create mode 100644 tests/robotests/src/com/android/settings/homepage/CardDatabaseHelperTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ccdc2dc0838..f400cfcdfc6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3148,6 +3148,12 @@ + + diff --git a/src/com/android/settings/homepage/CardContentProvider.java b/src/com/android/settings/homepage/CardContentProvider.java new file mode 100644 index 00000000000..3081ae1fa81 --- /dev/null +++ b/src/com/android/settings/homepage/CardContentProvider.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.Build; +import android.os.StrictMode; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +/** + * Provider stores and manages user interaction feedback for homepage contextual cards. + */ +public class CardContentProvider extends ContentProvider { + + private static final String TAG = "CardContentProvider"; + + public static final String CARD_AUTHORITY = "com.android.settings.homepage.CardContentProvider"; + + /** URI matcher for ContentProvider queries. */ + private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + /** URI matcher type for cards table */ + private static final int MATCH_CARDS = 100; + /** URI matcher type for card log table */ + private static final int MATCH_CARD_LOG = 200; + + static { + sUriMatcher.addURI(CARD_AUTHORITY, CardDatabaseHelper.CARD_TABLE, MATCH_CARDS); + } + + private CardDatabaseHelper mDBHelper; + + @Override + public boolean onCreate() { + mDBHelper = CardDatabaseHelper.getInstance(getContext()); + return true; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (Build.IS_DEBUGGABLE) { + enableStrictMode(true); + } + + final SQLiteDatabase database = mDBHelper.getWritableDatabase(); + final String table = getTableFromMatch(uri); + final long ret = database.insert(table, null, values); + if (ret != -1) { + getContext().getContentResolver().notifyChange(uri, null); + } else { + Log.e(TAG, "The CardContentProvider insertion failed! Plase check SQLiteDatabase's " + + "message."); + } + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + return uri; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (Build.IS_DEBUGGABLE) { + enableStrictMode(true); + } + + final SQLiteDatabase database = mDBHelper.getWritableDatabase(); + final String table = getTableFromMatch(uri); + final int rowsDeleted = database.delete(table, selection, selectionArgs); + getContext().getContentResolver().notifyChange(uri, null); + return rowsDeleted; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + @Override + public String getType(Uri uri) { + throw new UnsupportedOperationException("getType operation not supported currently."); + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (Build.IS_DEBUGGABLE) { + enableStrictMode(true); + } + + final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); + final String table = getTableFromMatch(uri); + queryBuilder.setTables(table); + final SQLiteDatabase database = mDBHelper.getReadableDatabase(); + final Cursor cursor = queryBuilder.query(database, + projection, selection, selectionArgs, null, null, sortOrder); + + cursor.setNotificationUri(getContext().getContentResolver(), uri); + return cursor; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + try { + if (Build.IS_DEBUGGABLE) { + enableStrictMode(true); + } + + final SQLiteDatabase database = mDBHelper.getWritableDatabase(); + final String table = getTableFromMatch(uri); + final int rowsUpdated = database.update(table, values, selection, selectionArgs); + getContext().getContentResolver().notifyChange(uri, null); + return rowsUpdated; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + private void enableStrictMode(boolean enabled) { + StrictMode.setThreadPolicy(enabled + ? new StrictMode.ThreadPolicy.Builder().detectAll().build() + : StrictMode.ThreadPolicy.LAX); + } + + @VisibleForTesting + String getTableFromMatch(Uri uri) { + final int match = sUriMatcher.match(uri); + String table; + switch (match) { + case MATCH_CARDS: + table = CardDatabaseHelper.CARD_TABLE; + break; + default: + throw new IllegalArgumentException("Unknown Uri format: " + uri); + } + return table; + } +} diff --git a/src/com/android/settings/homepage/CardDatabaseHelper.java b/src/com/android/settings/homepage/CardDatabaseHelper.java new file mode 100644 index 00000000000..b4dc221d39b --- /dev/null +++ b/src/com/android/settings/homepage/CardDatabaseHelper.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2018 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.homepage; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import androidx.annotation.VisibleForTesting; + +/** + * Defines the schema for the Homepage Cards database. + */ +public class CardDatabaseHelper extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "homepage_cards.db"; + private static final int DATABASE_VERSION = 1; + + public static final String CARD_TABLE = "cards"; + + public interface CardColumns { + /** + * Primary key. Name of the card. + */ + String NAME = "name"; + + /** + * Type of the card. + */ + String TYPE = "type"; + + /** + * Score of the card. Higher numbers have higher priorities. + */ + String SCORE = "score"; + + /** + * URI of the slice card. + */ + String SLICE_URI = "slice_uri"; + + /** + * Category of the card. The value is between 0 to 3. + */ + String CATEGORY = "category"; + + /** + * URI decides the card can be shown. + */ + String AVAILABILITY_URI = "availability_uri"; + + /** + * Keep the card last display's locale. + */ + String LOCALIZED_TO_LOCALE = "localized_to_locale"; + + /** + * Package name for all card candidates. + */ + String PACKAGE_NAME = "package_name"; + + /** + * Application version of the package. + */ + String APP_VERSION = "app_version"; + + /** + * Title resource name of the package. + */ + String TITLE_RES_NAME = "title_res_name"; + + /** + * Title of the package to be shown. + */ + String TITLE_TEXT = "title_text"; + + /** + * Summary resource name of the package. + */ + String SUMMARY_RES_NAME = "summary_res_name"; + + /** + * Summary of the package to be shown. + */ + String SUMMARY_TEXT = "summary_text"; + + /** + * Icon resource name of the package. + */ + String ICON_RES_NAME = "icon_res_name"; + + /** + * Icon resource id of the package. + */ + String ICON_RES_ID = "icon_res_id"; + + /** + * PendingIntent for for custom view card candidate. Do action when user press card. + */ + String CARD_ACTION = "card_action"; + + /** + * Expire time of the card. The unit of the value is mini-second. + */ + String EXPIRE_TIME_MS = "expire_time_ms"; + } + + private static final String CREATE_CARD_TABLE = + "CREATE TABLE " + CARD_TABLE + + "(" + + CardColumns.NAME + + " TEXT NOT NULL PRIMARY KEY, " + + CardColumns.TYPE + + " INTEGER NOT NULL, " + + CardColumns.SCORE + + " DOUBLE NOT NULL, " + + CardColumns.SLICE_URI + + " TEXT, " + + CardColumns.CATEGORY + + " INTEGER DEFAULT 0 CHECK (" + + CardColumns.CATEGORY + + " >= 0 AND " + + CardColumns.CATEGORY + + " <= 3), " + + CardColumns.AVAILABILITY_URI + + " TEXT, " + + CardColumns.LOCALIZED_TO_LOCALE + + " TEXT, " + + CardColumns.PACKAGE_NAME + + " TEXT NOT NULL, " + + CardColumns.APP_VERSION + + " TEXT NOT NULL, " + + CardColumns.TITLE_RES_NAME + + " TEXT, " + + CardColumns.TITLE_TEXT + + " TEXT, " + + CardColumns.SUMMARY_RES_NAME + + " TEXT, " + + CardColumns.SUMMARY_TEXT + + " TEXT, " + + CardColumns.ICON_RES_NAME + + " TEXT, " + + CardColumns.ICON_RES_ID + + " INTEGER DEFAULT 0, " + + CardColumns.CARD_ACTION + + " TEXT, " + + CardColumns.EXPIRE_TIME_MS + + " INTEGER " + + ");"; + + public CardDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_CARD_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + CARD_TABLE); + onCreate(db); + } + } + + @VisibleForTesting + static CardDatabaseHelper sCardDatabaseHelper; + + public static synchronized CardDatabaseHelper getInstance(Context context) { + if (sCardDatabaseHelper == null) { + sCardDatabaseHelper = new CardDatabaseHelper(context.getApplicationContext()); + } + return sCardDatabaseHelper; + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/CardContentProviderTest.java b/tests/robotests/src/com/android/settings/homepage/CardContentProviderTest.java new file mode 100644 index 00000000000..bf1527a4a42 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/CardContentProviderTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 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.homepage; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class CardContentProviderTest { + + private Context mContext; + private CardContentProvider mProvider; + private Uri mUri; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mProvider = Robolectric.setupContentProvider(CardContentProvider.class); + mUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(CardContentProvider.CARD_AUTHORITY) + .path(CardDatabaseHelper.CARD_TABLE) + .build(); + } + + @After + public void cleanUp() { + CardDatabaseHelper.getInstance(mContext).close(); + CardDatabaseHelper.sCardDatabaseHelper = null; + } + + @Test + public void cardData_insert() { + final int cnt_before_instert = getRowCount(); + mContext.getContentResolver().insert(mUri, insertOneRow()); + final int cnt_after_instert = getRowCount(); + + assertThat(cnt_after_instert - cnt_before_instert).isEqualTo(1); + } + + @Test + public void cardData_query() { + mContext.getContentResolver().insert(mUri, insertOneRow()); + final int count = getRowCount(); + + assertThat(count).isGreaterThan(0); + } + + @Test + public void cardData_delete() { + final ContentResolver contentResolver = mContext.getContentResolver(); + contentResolver.insert(mUri, insertOneRow()); + final int del_count = contentResolver.delete(mUri, null, null); + + assertThat(del_count).isGreaterThan(0); + } + + @Test + public void cardData_update() { + final ContentResolver contentResolver = mContext.getContentResolver(); + contentResolver.insert(mUri, insertOneRow()); + + final double updatingScore= 0.87; + final ContentValues values = new ContentValues(); + values.put(CardDatabaseHelper.CardColumns.SCORE, updatingScore); + final String strWhere = CardDatabaseHelper.CardColumns.NAME + "=?"; + final String[] selectionArgs = {"auto_rotate"}; + final int update_count = contentResolver.update(mUri, values, strWhere, selectionArgs); + + assertThat(update_count).isGreaterThan(0); + + final String[] columns = {CardDatabaseHelper.CardColumns.SCORE}; + final Cursor cr = contentResolver.query(mUri, columns, strWhere, selectionArgs, null); + cr.moveToFirst(); + final double qryScore = cr.getDouble(0); + + cr.close(); + assertThat(qryScore).isEqualTo(updatingScore); + } + + @Test(expected = UnsupportedOperationException.class) + public void getType_shouldCrash() { + mProvider.getType(null); + } + + @Test(expected = IllegalArgumentException.class) + public void invalid_Uri_shouldCrash() { + final Uri invalid_Uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(CardContentProvider.CARD_AUTHORITY) + .path("Invalid_table") + .build(); + + mProvider.getTableFromMatch(invalid_Uri); + } + + private ContentValues insertOneRow() { + final ContentValues values = new ContentValues(); + values.put(CardDatabaseHelper.CardColumns.NAME, "auto_rotate"); + values.put(CardDatabaseHelper.CardColumns.TYPE, 0); + values.put(CardDatabaseHelper.CardColumns.SCORE, 0.9); + values.put(CardDatabaseHelper.CardColumns.SLICE_URI, + "content://com.android.settings.slices/action/auto_rotate"); + values.put(CardDatabaseHelper.CardColumns.CATEGORY, 2); + values.put(CardDatabaseHelper.CardColumns.PACKAGE_NAME, "com.android.settings"); + values.put(CardDatabaseHelper.CardColumns.APP_VERSION, "1.0.0"); + + return values; + } + + private int getRowCount() { + final ContentResolver contentResolver = mContext.getContentResolver(); + final Cursor cr = contentResolver.query(mUri, null, null, null); + final int count = cr.getCount(); + cr.close(); + return count; + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/CardDatabaseHelperTest.java b/tests/robotests/src/com/android/settings/homepage/CardDatabaseHelperTest.java new file mode 100644 index 00000000000..b6ed358a5d5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/CardDatabaseHelperTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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.homepage; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class CardDatabaseHelperTest { + + private Context mContext; + private CardDatabaseHelper mCardDatabaseHelper; + private SQLiteDatabase mDatabase; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mCardDatabaseHelper = CardDatabaseHelper.getInstance(mContext); + mDatabase = mCardDatabaseHelper.getWritableDatabase(); + } + + @After + public void cleanUp() { + CardDatabaseHelper.getInstance(mContext).close(); + CardDatabaseHelper.sCardDatabaseHelper = null; + } + + @Test + public void testDatabaseSchema() { + final Cursor cursor = mDatabase.rawQuery("SELECT * FROM " + CardDatabaseHelper.CARD_TABLE, + null); + final String[] columnNames = cursor.getColumnNames(); + + final String[] expectedNames = { + CardDatabaseHelper.CardColumns.NAME, + CardDatabaseHelper.CardColumns.TYPE, + CardDatabaseHelper.CardColumns.SCORE, + CardDatabaseHelper.CardColumns.SLICE_URI, + CardDatabaseHelper.CardColumns.CATEGORY, + CardDatabaseHelper.CardColumns.AVAILABILITY_URI, + CardDatabaseHelper.CardColumns.LOCALIZED_TO_LOCALE, + CardDatabaseHelper.CardColumns.PACKAGE_NAME, + CardDatabaseHelper.CardColumns.APP_VERSION, + CardDatabaseHelper.CardColumns.TITLE_RES_NAME, + CardDatabaseHelper.CardColumns.TITLE_TEXT, + CardDatabaseHelper.CardColumns.SUMMARY_RES_NAME, + CardDatabaseHelper.CardColumns.SUMMARY_TEXT, + CardDatabaseHelper.CardColumns.ICON_RES_NAME, + CardDatabaseHelper.CardColumns.ICON_RES_ID, + CardDatabaseHelper.CardColumns.CARD_ACTION, + CardDatabaseHelper.CardColumns.EXPIRE_TIME_MS, + }; + + assertThat(columnNames).isEqualTo(expectedNames); + cursor.close(); + } +} From 8ee474a3690534d4490a43df0f01dc9bfa9fcb17 Mon Sep 17 00:00:00 2001 From: hughchen Date: Tue, 7 Aug 2018 18:00:28 +0800 Subject: [PATCH 22/25] Clear connected Bluetooth device from preference when Bluetooth state is off 1. Clear connected Bluetooth device from preference when Bluetooth state is off. 2. Do not force to update the list of Bluetooth device when Bluetooth is disable. 3. Add test to verify following situations: 1. Do not force to update the list of Bluetooth device when Bluetooth is disable. 2. Force to update the list of Bluetooth device when Bluetooth is enable. 3. Force to update the list of Bluetooth device when Bluetooth state is on. 4. Clear the connected Bluetooth device from preference when Bluetooth state is off. Bug: 110178164 Test: make -j42 RunSettingsRoboTests Change-Id: I8b17c5d761e010e4eab620355c8b9185543e85ed --- .../bluetooth/BluetoothDeviceUpdater.java | 21 ++++++-- ...ilableMediaBluetoothDeviceUpdaterTest.java | 8 ++- .../bluetooth/BluetoothDeviceUpdaterTest.java | 52 ++++++++++++++++++- .../ConnectedBluetoothDeviceUpdaterTest.java | 8 ++- 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index dece0ccccf6..69708e7a77b 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -15,6 +15,7 @@ */ package com.android.settings.bluetooth; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.Bundle; @@ -115,16 +116,30 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, * Force to update the list of bluetooth devices */ public void forceUpdate() { - Collection cachedDevices = + if (BluetoothAdapter.getDefaultAdapter().isEnabled()) { + final Collection cachedDevices = + mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); + for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) { + update(cachedBluetoothDevice); + } + } + } + + public void removeAllDevicesFromPreference() { + final Collection cachedDevices = mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) { - update(cachedBluetoothDevice); + removePreference(cachedBluetoothDevice); } } @Override public void onBluetoothStateChanged(int bluetoothState) { - forceUpdate(); + if (BluetoothAdapter.STATE_ON == bluetoothState) { + forceUpdate(); + } else if (BluetoothAdapter.STATE_OFF == bluetoothState) { + removeAllDevicesFromPreference(); + } } @Override diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index cf73d41eb78..e676cf4b041 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -31,6 +32,7 @@ import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowAudioManager; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HeadsetProfile; @@ -44,12 +46,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.Collection; @RunWith(SettingsRobolectricTestRunner.class) -@Config(shadows = {ShadowAudioManager.class}) +@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class}) public class AvailableMediaBluetoothDeviceUpdaterTest { @Mock private DashboardFragment mDashboardFragment; @@ -73,12 +76,15 @@ public class AvailableMediaBluetoothDeviceUpdaterTest { private Collection cachedDevices; private ShadowAudioManager mShadowAudioManager; private BluetoothDevicePreference mPreference; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; @Before public void setUp() { MockitoAnnotations.initMocks(this); mShadowAudioManager = ShadowAudioManager.getShadow(); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mShadowBluetoothAdapter.setEnabled(true); mContext = RuntimeEnvironment.application; doReturn(mContext).when(mDashboardFragment).getContext(); cachedDevices = diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java index 9f81711c88c..f338e3668d2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceUpdaterTest.java @@ -22,15 +22,19 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; +import androidx.preference.Preference; import com.android.settings.SettingsActivity; import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; import org.junit.Before; @@ -40,10 +44,14 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; -import androidx.preference.Preference; +import java.util.ArrayList; +import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) public class BluetoothDeviceUpdaterTest { @Mock @@ -58,18 +66,26 @@ public class BluetoothDeviceUpdaterTest { private SettingsActivity mSettingsActivity; @Mock private LocalBluetoothManager mLocalManager; + @Mock + private CachedBluetoothDeviceManager mCachedDeviceManager; private Context mContext; private BluetoothDeviceUpdater mBluetoothDeviceUpdater; private BluetoothDevicePreference mPreference; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; + private List mCachedDevices = new ArrayList(); @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mCachedDevices.add(mCachedBluetoothDevice); doReturn(mContext).when(mDashboardFragment).getContext(); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mLocalManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices); mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, false); mBluetoothDeviceUpdater = @@ -171,4 +187,38 @@ public class BluetoothDeviceUpdaterTest { // Shouldn't crash mBluetoothDeviceUpdater.unregisterCallback(); } + + @Test + public void forceUpdate_bluetoothDisabled_doNothing() { + mShadowBluetoothAdapter.setEnabled(false); + mBluetoothDeviceUpdater.forceUpdate(); + + verify(mDevicePreferenceCallback, never()).onDeviceAdded(any(Preference.class)); + } + + @Test + public void forceUpdate_bluetoothEnabled_addPreference() { + mShadowBluetoothAdapter.setEnabled(true); + mBluetoothDeviceUpdater.forceUpdate(); + + verify(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class)); + } + + @Test + public void onBluetoothStateChanged_bluetoothStateIsOn_forceUpdate() { + mShadowBluetoothAdapter.setEnabled(true); + mBluetoothDeviceUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_ON); + + verify(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class)); + } + + @Test + public void onBluetoothStateChanged_bluetoothStateIsOff_removeAllDevicesFromPreference() { + mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, mPreference); + + mBluetoothDeviceUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF); + + verify(mDevicePreferenceCallback).onDeviceRemoved(mPreference); + assertThat(mBluetoothDeviceUpdater.mPreferenceMap.containsKey(mBluetoothDevice)).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index d94a8a7cf0a..ece71d73d9c 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -32,6 +33,7 @@ import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowAudioManager; +import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -43,12 +45,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.Collection; @RunWith(SettingsRobolectricTestRunner.class) -@Config(shadows = {ShadowAudioManager.class}) +@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class}) public class ConnectedBluetoothDeviceUpdaterTest { @Mock private DashboardFragment mDashboardFragment; @@ -67,12 +70,15 @@ public class ConnectedBluetoothDeviceUpdaterTest { private ConnectedBluetoothDeviceUpdater mBluetoothDeviceUpdater; private Collection cachedDevices; private ShadowAudioManager mShadowAudioManager; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; @Before public void setUp() { MockitoAnnotations.initMocks(this); mShadowAudioManager = ShadowAudioManager.getShadow(); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mShadowBluetoothAdapter.setEnabled(true); mContext = RuntimeEnvironment.application; doReturn(mContext).when(mDashboardFragment).getContext(); cachedDevices = From 7d5a9eebb84137ac02783be0812e44c8e872ecf4 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 9 Aug 2018 17:32:37 -0700 Subject: [PATCH 23/25] Add a config to force rounded icon for DashboardFragment. And each page has ability to turn on/off rounded icons. This CL only adds the flag, it doesn't actually change icon shape yet. - Boolean config in xml - New protected method for each DashboardFragment to load config - Plumb the boolean into DashboardFeatureProvider for future use. - Remove some unused APIs from DashboardFeatureProvider Bug: 110405144 Fixes: 79748104 Test: robotests Change-Id: Id34782e75aa7289967e4dd1f4fe2978688092702 --- res/values/config.xml | 6 +- .../settings/dashboard/DashboardAdapter.java | 1 + .../dashboard/DashboardFeatureProvider.java | 30 +----- .../DashboardFeatureProviderImpl.java | 32 +------ .../settings/dashboard/DashboardFragment.java | 15 ++- .../settings/dashboard/SummaryLoader.java | 6 -- .../settings/homepage/TopLevelSettings.java | 6 ++ .../RoundedHomepageIcon.java | 4 +- tests/robotests/res/values-mcc999/config.xml | 1 - .../AccountDetailDashboardFragmentTest.java | 2 +- .../dashboard/DashboardAdapterTest.java | 2 +- .../DashboardFeatureProviderImplTest.java | 95 ++++--------------- .../homepage/TopLevelSettingsTest.java | 49 ++++++++++ .../RoundedHomepageIconTest.java | 3 +- 14 files changed, 102 insertions(+), 150 deletions(-) rename src/com/android/settings/{dashboard => widget}/RoundedHomepageIcon.java (95%) create mode 100644 tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java rename tests/robotests/src/com/android/settings/{dashboard => widget}/RoundedHomepageIconTest.java (98%) diff --git a/res/values/config.xml b/res/values/config.xml index 2920ac839d4..f5105232035 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -106,9 +106,6 @@ --> - - true - true @@ -133,4 +130,7 @@ devices will be able to vary their amplitude but do not possess enough dynamic range to have distinct intensity levels --> false + + + true diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 7e7cf46caf4..5cc1b3b6712 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -44,6 +44,7 @@ import com.android.settings.dashboard.conditional.Condition; import com.android.settings.dashboard.conditional.ConditionAdapter; import com.android.settings.dashboard.suggestions.SuggestionAdapter; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.widget.RoundedHomepageIcon; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; diff --git a/src/com/android/settings/dashboard/DashboardFeatureProvider.java b/src/com/android/settings/dashboard/DashboardFeatureProvider.java index ac11e0bb497..81fb99e83be 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProvider.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProvider.java @@ -15,8 +15,6 @@ */ package com.android.settings.dashboard; -import android.content.Context; - import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; @@ -35,34 +33,11 @@ public interface DashboardFeatureProvider { */ DashboardCategory getTilesForCategory(String key); - /** - * Get tiles (wrapped as a list of Preference) for key defined in CategoryKey. - * - * @param activity Activity hosting the preference - * @param context UI context to inflate preference - * @param sourceMetricsCategory The context (source) from which an action is performed - * @param key Value from CategoryKey - * @deprecated Pages implementing {@code DashboardFragment} should use - * {@link #getTilesForCategory(String)} instead. Using this method will not get the benefit - * of auto-ordering, progressive disclosure, auto-refreshing summary text etc. - */ - @Deprecated - List getPreferencesForCategory(FragmentActivity activity, Context context, - int sourceMetricsCategory, String key); - /** * Get all tiles, grouped by category. */ List getAllCategories(); - /** - * Whether or not we should tint icons in setting pages. - * - * @deprecated in favor of color icons in homepage - */ - @Deprecated - boolean shouldTintIcon(); - /** * Returns an unique string key for the tile. */ @@ -72,6 +47,7 @@ public interface DashboardFeatureProvider { * Binds preference to data provided by tile. * * @param activity If tile contains intent to launch, it will be launched from this activity + * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to rounded icon. * @param sourceMetricsCategory The context (source) from which an action is performed * @param pref The preference to bind data * @param tile The binding data @@ -79,8 +55,8 @@ public interface DashboardFeatureProvider { * @param baseOrder The order offset value. When binding, pref's order is determined by * both this value and tile's own priority. */ - void bindPreferenceToTile(FragmentActivity activity, int sourceMetricsCategory, Preference pref, - Tile tile, String key, int baseOrder); + void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, + int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder); /** * Returns additional intent filter action for dashboard tiles diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index e4dd7a61aa9..6c575189fc1 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -80,39 +80,11 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { return mCategoryManager.getTilesByCategory(mContext, key); } - @Override - public List getPreferencesForCategory(FragmentActivity activity, Context context, - int sourceMetricsCategory, String key) { - final DashboardCategory category = getTilesForCategory(key); - if (category == null) { - Log.d(TAG, "NO dashboard tiles for " + TAG); - return null; - } - final List tiles = category.getTiles(); - if (tiles == null || tiles.isEmpty()) { - Log.d(TAG, "tile list is empty, skipping category " + category.key); - return null; - } - final List preferences = new ArrayList<>(); - for (Tile tile : tiles) { - final Preference pref = new Preference(context); - bindPreferenceToTile(activity, sourceMetricsCategory, pref, tile, null /* key */, - Preference.DEFAULT_ORDER /* baseOrder */); - preferences.add(pref); - } - return preferences; - } - @Override public List getAllCategories() { return mCategoryManager.getCategories(mContext); } - @Override - public boolean shouldTintIcon() { - return mContext.getResources().getBoolean(R.bool.config_tintSettingIcon); - } - @Override public String getDashboardKeyForTile(Tile tile) { if (tile == null) { @@ -128,8 +100,8 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } @Override - public void bindPreferenceToTile(FragmentActivity activity, int sourceMetricsCategory, - Preference pref, Tile tile, String key, int baseOrder) { + public void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, + int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) { if (pref == null) { return; } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index acf885dee0f..a90950e5521 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -207,6 +207,10 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment @Override protected abstract int getPreferenceScreenResId(); + protected boolean shouldForceRoundedIcon() { + return false; + } + protected T use(Class clazz) { List controllerList = mPreferenceControllers.get(clazz); if (controllerList != null) { @@ -343,6 +347,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment final int tintColor = a.getColor(0, context.getColor(android.R.color.white)); a.recycle(); // Install dashboard tiles. + final boolean forceRoundedIcons = shouldForceRoundedIcon(); for (Tile tile : tiles) { final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); if (TextUtils.isEmpty(key)) { @@ -361,13 +366,15 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment if (mDashboardTilePrefKeys.contains(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); - mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(), - preference, tile, key, mPlaceholderPreferenceController.getOrder()); + mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons, + getMetricsCategory(), preference, tile, key, + mPlaceholderPreferenceController.getOrder()); } else { // Don't have this key, add it. final Preference pref = new Preference(getPrefContext()); - mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), getMetricsCategory(), - pref, tile, key, mPlaceholderPreferenceController.getOrder()); + mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons, + getMetricsCategory(), pref, tile, key, + mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); mDashboardTilePrefKeys.add(key); } diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java index c85aad76b79..199331d5b21 100644 --- a/src/com/android/settings/dashboard/SummaryLoader.java +++ b/src/com/android/settings/dashboard/SummaryLoader.java @@ -42,12 +42,6 @@ import com.android.settingslib.utils.ThreadUtils; import java.lang.reflect.Field; import java.util.List; -/** - * TODO(b/110405144): Remove this when all top level settings are converted to PreferenceControllers - * - * @deprecated - */ -@Deprecated public class SummaryLoader { private static final boolean DEBUG = DashboardSummary.DEBUG; private static final String TAG = "SummaryLoader"; diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index 5c682cc12df..c450157d254 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -91,6 +91,12 @@ public class TopLevelSettings extends DashboardFragment implements return true; } + @Override + protected boolean shouldForceRoundedIcon() { + return getContext().getResources() + .getBoolean(R.bool.config_force_rounded_icon_TopLevelSettings); + } + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override diff --git a/src/com/android/settings/dashboard/RoundedHomepageIcon.java b/src/com/android/settings/widget/RoundedHomepageIcon.java similarity index 95% rename from src/com/android/settings/dashboard/RoundedHomepageIcon.java rename to src/com/android/settings/widget/RoundedHomepageIcon.java index 9848034d7e5..ab0dfecd4e7 100644 --- a/src/com/android/settings/dashboard/RoundedHomepageIcon.java +++ b/src/com/android/settings/widget/RoundedHomepageIcon.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.dashboard; +package com.android.settings.widget; import static androidx.annotation.VisibleForTesting.NONE; @@ -33,7 +33,7 @@ public class RoundedHomepageIcon extends LayerDrawable { private static final String TAG = "RoundedHomepageIcon"; @VisibleForTesting(otherwise = NONE) - int mBackgroundColor = -1; + public int mBackgroundColor = -1; public RoundedHomepageIcon(Context context, Drawable foreground) { super(new Drawable[] { diff --git a/tests/robotests/res/values-mcc999/config.xml b/tests/robotests/res/values-mcc999/config.xml index 676a8dd0c71..bbbdcc2e849 100644 --- a/tests/robotests/res/values-mcc999/config.xml +++ b/tests/robotests/res/values-mcc999/config.xml @@ -15,7 +15,6 @@ --> - false false false false diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java index 6b19e59d43e..eca9f862b30 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java @@ -130,7 +130,7 @@ public class AccountDetailDashboardFragmentTest { final FragmentActivity activity = Robolectric.setupActivity(FragmentActivity.class); final Preference preference = new Preference(mContext); - dashboardFeatureProvider.bindPreferenceToTile(activity, + dashboardFeatureProvider.bindPreferenceToTile(activity, false /* forceRoundedIcon */, MetricsProto.MetricsEvent.DASHBOARD_SUMMARY, preference, tile, null /* key */, Preference.DEFAULT_ORDER); diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java index 8f10af7afec..0f84be1e8e9 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java @@ -50,6 +50,7 @@ import com.android.settings.dashboard.suggestions.SuggestionAdapter; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; +import com.android.settings.widget.RoundedHomepageIcon; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; @@ -95,7 +96,6 @@ public class DashboardAdapterTest { mActivityInfo.packageName = "pkg"; mActivityInfo.name = "class"; mActivityInfo.metaData = new Bundle(); - when(mFactory.dashboardFeatureProvider.shouldTintIcon()).thenReturn(true); when(mContext.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mWindowManager); when(mContext.getResources()).thenReturn(mResources); diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java index adc1ac0ff6e..dbaf8fe5b30 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java @@ -49,7 +49,7 @@ import android.os.UserManager; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; -import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.FakeFeatureFactory; @@ -59,7 +59,6 @@ import com.android.settings.testutils.shadow.ShadowTileUtils; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.drawer.CategoryKey; -import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; @@ -88,19 +87,19 @@ public class DashboardFeatureProviderImplTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private UserManager mUserManager; @Mock - private CategoryManager mCategoryManager; - @Mock private PackageManager mPackageManager; private FakeFeatureFactory mFeatureFactory; private Context mContext; private ActivityInfo mActivityInfo; private DashboardFeatureProviderImpl mImpl; + private boolean mForceRoundedIcon; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mForceRoundedIcon = false; mActivityInfo = new ActivityInfo(); mActivityInfo.packageName = "pkg"; mActivityInfo.name = "class"; @@ -127,7 +126,7 @@ public class DashboardFeatureProviderImplTest { doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565))) .when(tile).getIcon(any(Context.class)); mActivityInfo.metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS, "HI"); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getTitle()).isEqualTo(tile.title); @@ -144,7 +143,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getFragment()).isNull(); @@ -163,7 +162,7 @@ public class DashboardFeatureProviderImplTest { when(mActivity.getApplicationContext().getSystemService(Context.USER_SERVICE)) .thenReturn(mUserManager); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); preference.getOnPreferenceClickListener().onPreferenceClick(null); @@ -180,14 +179,14 @@ public class DashboardFeatureProviderImplTest { when(mActivity.getSystemService(Context.USER_SERVICE)) .thenReturn(mUserManager); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); preference.getOnPreferenceClickListener().onPreferenceClick(null); verify(mFeatureFactory.metricsFeatureProvider).logDashboardStartIntent( any(Context.class), any(Intent.class), - eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES)); + eq(MetricsEvent.SETTINGS_GESTURES)); verify(mActivity) .startActivityForResultAsUser(any(Intent.class), anyInt(), any(UserHandle.class)); } @@ -205,7 +204,7 @@ public class DashboardFeatureProviderImplTest { when(mActivity.getApplicationContext().getPackageName()) .thenReturn(RuntimeEnvironment.application.getPackageName()); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); preference.getOnPreferenceClickListener().onPreferenceClick(null); verify(mFeatureFactory.metricsFeatureProvider).logDashboardStartIntent( @@ -219,7 +218,7 @@ public class DashboardFeatureProviderImplTest { @Test public void bindPreference_nullPreference_shouldIgnore() { final Tile tile = mock(Tile.class); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, null, tile, "123", Preference.DEFAULT_ORDER); verifyZeroInteractions(tile); @@ -229,7 +228,7 @@ public class DashboardFeatureProviderImplTest { public void bindPreference_withNullKeyNullPriority_shouldGenerateKeyAndPriority() { final Preference preference = new Preference(RuntimeEnvironment.application); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, Preference.DEFAULT_ORDER); assertThat(preference.getKey()).isNotNull(); @@ -240,7 +239,7 @@ public class DashboardFeatureProviderImplTest { public void bindPreference_noSummary_shouldSetSummaryToPlaceholder() { final Preference preference = new Preference(RuntimeEnvironment.application); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, Preference.DEFAULT_ORDER); assertThat(preference.getSummary()) @@ -252,7 +251,7 @@ public class DashboardFeatureProviderImplTest { final Preference preference = new Preference(RuntimeEnvironment.application); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); tile.summary = "test"; - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, Preference.DEFAULT_ORDER); assertThat(preference.getSummary()).isEqualTo(tile.summary); @@ -266,7 +265,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, "content://com.android.settings/tile_summary"); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, Preference.DEFAULT_ORDER); assertThat(preference.getSummary()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY); @@ -277,7 +276,7 @@ public class DashboardFeatureProviderImplTest { final Preference preference = new Preference(RuntimeEnvironment.application); mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key"); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, null /* key */, Preference.DEFAULT_ORDER); assertThat(preference.getKey()).isEqualTo(tile.getKey(mContext)); @@ -304,7 +303,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", baseOrder); assertThat(preference.getOrder()).isEqualTo(tile.getOrder() + baseOrder); @@ -317,7 +316,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10); final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, testOrder); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getOrder()).isEqualTo(testOrder); @@ -329,7 +328,7 @@ public class DashboardFeatureProviderImplTest { final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); mActivityInfo.metaData.putString(META_DATA_KEY_ORDER, "hello"); - mImpl.bindPreferenceToTile(mActivity, MetricsProto.MetricsEvent.VIEW_UNKNOWN, + mImpl.bindPreferenceToTile(mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getOrder()).isEqualTo(Preference.DEFAULT_ORDER); @@ -343,7 +342,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key"); mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction"); tile.userHandle = null; - mImpl.bindPreferenceToTile(activity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(activity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); preference.performClick(); ShadowActivity shadowActivity = Shadows.shadowOf(activity); @@ -352,7 +351,7 @@ public class DashboardFeatureProviderImplTest { assertThat(launchIntent.getAction()) .isEqualTo("TestAction"); assertThat(launchIntent.getIntExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, 0)) - .isEqualTo(MetricsProto.MetricsEvent.SETTINGS_GESTURES); + .isEqualTo(MetricsEvent.SETTINGS_GESTURES); } @Test @@ -367,7 +366,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction"); tile.userHandle = null; - mImpl.bindPreferenceToTile(activity, MetricsProto.MetricsEvent.SETTINGS_GESTURES, + mImpl.bindPreferenceToTile(activity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); preference.performClick(); @@ -377,63 +376,11 @@ public class DashboardFeatureProviderImplTest { assertThat(launchIntent).isNull(); } - @Test - public void getPreferences_noCategory_shouldReturnNull() { - mImpl = new DashboardFeatureProviderImpl(mActivity); - ReflectionHelpers.setField(mImpl, "mCategoryManager", mCategoryManager); - when(mCategoryManager.getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE)) - .thenReturn(null); - - assertThat(mImpl.getPreferencesForCategory(null, null, - MetricsProto.MetricsEvent.SETTINGS_GESTURES, CategoryKey.CATEGORY_HOMEPAGE)) - .isNull(); - } - - @Test - public void getPreferences_noTileForCategory_shouldReturnNull() { - mImpl = new DashboardFeatureProviderImpl(mActivity); - ReflectionHelpers.setField(mImpl, "mCategoryManager", mCategoryManager); - when(mCategoryManager.getTilesByCategory(mActivity, CategoryKey.CATEGORY_HOMEPAGE)) - .thenReturn(new DashboardCategory(CategoryKey.CATEGORY_HOMEPAGE)); - - assertThat(mImpl.getPreferencesForCategory(null, null, - MetricsProto.MetricsEvent.SETTINGS_GESTURES, CategoryKey.CATEGORY_HOMEPAGE)) - .isNull(); - } - - @Test - public void getPreferences_hasTileForCategory_shouldReturnPrefList() { - mImpl = new DashboardFeatureProviderImpl(mActivity); - ReflectionHelpers.setField(mImpl, "mCategoryManager", mCategoryManager); - final DashboardCategory category = new DashboardCategory(CategoryKey.CATEGORY_HOMEPAGE); - category.addTile(new Tile(mActivityInfo, category.key)); - when(mCategoryManager - .getTilesByCategory(any(Context.class), eq(CategoryKey.CATEGORY_HOMEPAGE))) - .thenReturn(category); - - assertThat(mImpl.getPreferencesForCategory(mActivity, - ShadowApplication.getInstance().getApplicationContext(), - MetricsProto.MetricsEvent.SETTINGS_GESTURES, - CategoryKey.CATEGORY_HOMEPAGE).isEmpty()) - .isFalse(); - } - @Test public void testGetExtraIntentAction_shouldReturnNull() { assertThat(mImpl.getExtraIntentAction()).isNull(); } - @Test - public void testShouldTintIcon_enabledInResources_shouldBeTrue() { - assertThat(mImpl.shouldTintIcon()).isTrue(); - } - - @Test - @Config(qualifiers = "mcc999") - public void testShouldTintIcon_disabledInResources_shouldBeFalse() { - assertThat(mImpl.shouldTintIcon()).isFalse(); - } - @Test public void openTileIntent_profileSelectionDialog_shouldShow() { final Tile tile = new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); diff --git a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java new file mode 100644 index 00000000000..83ab948935e --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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.homepage; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingsRobolectricTestRunner.class) +public class TopLevelSettingsTest { + private Context mContext; + private TopLevelSettings mSettings; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mSettings = spy(new TopLevelSettings()); + when(mSettings.getContext()).thenReturn(mContext); + } + + @Test + public void shouldForceRoundedIcon_true() { + assertThat(mSettings.shouldForceRoundedIcon()).isTrue(); + } +} diff --git a/tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java b/tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java rename to tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java index 0c90660b114..177dba0083e 100644 --- a/tests/robotests/src/com/android/settings/dashboard/RoundedHomepageIconTest.java +++ b/tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.settings.dashboard; +package com.android.settings.widget; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; From fde4f207ff6a0f2378c8c696374fd94af1e7b20d Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Fri, 10 Aug 2018 15:37:41 -0700 Subject: [PATCH 24/25] Force externally injected tiles to use rounded icon. Bug: 110405144 Change-Id: Ic65200fce5010ea8077254e7a67bbe4dae886ec3 Fixes: 79748104 Test: robotests --- res/xml/top_level_settings.xml | 14 ++++- .../settings/dashboard/DashboardAdapter.java | 34 +---------- .../DashboardFeatureProviderImpl.java | 15 +++-- .../dashboard/DashboardFragmentRegistry.java | 5 +- .../settings/widget/RoundedHomepageIcon.java | 41 +++++++++++-- .../dashboard/DashboardAdapterTest.java | 60 ++----------------- .../DashboardFeatureProviderImplTest.java | 2 +- .../widget/RoundedHomepageIconTest.java | 44 ++++++++++++++ 8 files changed, 116 insertions(+), 99 deletions(-) diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index 171fe7867d6..6c91aeae70b 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -26,6 +26,7 @@ android:title="@string/network_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_network" + android:order="-110" android:fragment="com.android.settings.network.NetworkDashboardFragment" settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/> @@ -34,6 +35,7 @@ android:title="@string/connected_devices_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_connected_device" + android:order="-100" android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment" settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/> @@ -42,6 +44,7 @@ android:title="@string/app_and_notification_dashboard_title" android:summary="@string/app_and_notification_dashboard_summary" android:icon="@drawable/ic_homepage_apps" + android:order="-90" android:fragment="com.android.settings.applications.AppAndNotificationDashboardFragment"/> @@ -79,6 +86,7 @@ android:title="@string/security_settings_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_security" + android:order="-40" android:fragment="com.android.settings.security.SecuritySettings" settings:controller="com.android.settings.security.TopLevelSecurityEntryPreferenceController"/> @@ -87,6 +95,7 @@ android:title="@string/account_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_accounts" + android:order="-30" android:fragment="com.android.settings.accounts.AccountDashboardFragment" settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/> @@ -95,6 +104,7 @@ android:title="@string/accessibility_settings" android:summary="@string/accessibility_settings_summary" android:icon="@drawable/ic_homepage_accessibility" + android:order="-20" android:fragment="com.android.settings.accessibility.AccessibilitySettings"/> + android:icon="@drawable/ic_homepage_support" + android:order="100"/> \ No newline at end of file diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 5cc1b3b6712..c11c668d8ba 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -16,7 +16,6 @@ package com.android.settings.dashboard; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -51,7 +50,6 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; -import com.android.settingslib.drawer.TileUtils; import com.android.settingslib.suggestions.SuggestionControllerMixinCompat; import com.android.settingslib.utils.IconCache; @@ -65,7 +63,7 @@ public class DashboardAdapter extends RecyclerView.Adapter { diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java index 4c371ddfdc3..f5330a7b5eb 100644 --- a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java +++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java @@ -62,8 +62,9 @@ public class DashboardFragmentRegistry { static { PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>(); - PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(), - CategoryKey.CATEGORY_HOMEPAGE); + // TODO(b/110405144): Add the mapping when IA.homepage intent-filter is is removed. + // PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(), + // CategoryKey.CATEGORY_HOMEPAGE); PARENT_TO_CATEGORY_KEY_MAP.put( NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK); PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(), diff --git a/src/com/android/settings/widget/RoundedHomepageIcon.java b/src/com/android/settings/widget/RoundedHomepageIcon.java index ab0dfecd4e7..f7927e756ca 100644 --- a/src/com/android/settings/widget/RoundedHomepageIcon.java +++ b/src/com/android/settings/widget/RoundedHomepageIcon.java @@ -18,25 +18,31 @@ package com.android.settings.widget; import static androidx.annotation.VisibleForTesting.NONE; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; import android.util.Log; -import com.android.settings.R; - import androidx.annotation.VisibleForTesting; +import com.android.settings.R; +import com.android.settingslib.drawer.Tile; + public class RoundedHomepageIcon extends LayerDrawable { private static final String TAG = "RoundedHomepageIcon"; @VisibleForTesting(otherwise = NONE) - public int mBackgroundColor = -1; + int mBackgroundColor = -1; public RoundedHomepageIcon(Context context, Drawable foreground) { - super(new Drawable[] { + super(new Drawable[]{ context.getDrawable(R.drawable.ic_homepage_generic_background), foreground }); @@ -45,6 +51,33 @@ public class RoundedHomepageIcon extends LayerDrawable { setLayerInset(1 /* index */, insetPx, insetPx, insetPx, insetPx); } + public void setBackgroundColor(Context context, Tile tile) { + final Bundle metaData = tile.getMetaData(); + try { + if (metaData != null) { + // Load from bg.argb first + int bgColor = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, + 0 /* default */); + // Not found, load from bg.hint + if (bgColor == 0) { + final int colorRes = metaData.getInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + 0 /* default */); + if (colorRes != 0) { + bgColor = context.getPackageManager() + .getResourcesForApplication(tile.getPackageName()) + .getColor(colorRes, null /* theme */); + } + } + // If found anything, use it. + if (bgColor != 0) { + setBackgroundColor(bgColor); + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to set background color for " + tile.getPackageName()); + } + } + public void setBackgroundColor(int color) { mBackgroundColor = color; getDrawable(0).setColorFilter(color, PorterDuff.Mode.SRC_ATOP); diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java index 0f84be1e8e9..df6a1a3b713 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java @@ -53,7 +53,6 @@ import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.widget.RoundedHomepageIcon; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.Tile; -import com.android.settingslib.drawer.TileUtils; import com.android.settingslib.utils.IconCache; import org.junit.Before; @@ -115,7 +114,7 @@ public class DashboardAdapterTest { spy(new DashboardAdapter(mContext, null /* savedInstanceState */, null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */)); - final List suggestions = makeSuggestionsV2("pkg1", "pkg2", "pkg3"); + final List suggestions = makeSuggestions("pkg1", "pkg2", "pkg3"); adapter.setSuggestions(suggestions); final RecyclerView data = mock(RecyclerView.class); @@ -147,7 +146,7 @@ public class DashboardAdapterTest { spy(new DashboardAdapter(mContext, null /* savedInstanceState */, null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */)); - final List suggestions = makeSuggestionsV2("pkg1"); + final List suggestions = makeSuggestions("pkg1"); adapter.setSuggestions(suggestions); final DashboardData dashboardData = adapter.mDashboardData; reset(adapter); // clear interactions tracking @@ -164,7 +163,7 @@ public class DashboardAdapterTest { spy(new DashboardAdapter(mContext, null /* savedInstanceState */, null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */)); - final List suggestions = makeSuggestionsV2("pkg1"); + final List suggestions = makeSuggestions("pkg1"); adapter.setSuggestions(suggestions); reset(adapter); // clear interactions tracking @@ -178,7 +177,7 @@ public class DashboardAdapterTest { public void onBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() { mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */, null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); - final List suggestions = makeSuggestionsV2("pkg1"); + final List suggestions = makeSuggestions("pkg1"); mDashboardAdapter.setSuggestions(suggestions); @@ -243,55 +242,6 @@ public class DashboardAdapterTest { .isInstanceOf(RoundedHomepageIcon.class); } - @Test - public void onBindTile_externalTileWithBackgroundColorRawValue_shouldUpdateIcon() { - final Context context = spy(RuntimeEnvironment.application); - final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); - final DashboardAdapter.DashboardItemHolder holder = - new DashboardAdapter.DashboardItemHolder(view); - final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); - tile.getMetaData().putInt(DashboardAdapter.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, - 0xff0000); - doReturn(Icon.createWithResource(context, R.drawable.ic_settings)) - .when(tile).getIcon(context); - final IconCache iconCache = new IconCache(context); - mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); - ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); - - doReturn("another.package").when(context).getPackageName(); - mDashboardAdapter.onBindTile(holder, tile); - - final RoundedHomepageIcon homepageIcon = (RoundedHomepageIcon) iconCache.getIcon( - tile.getIcon(context)); - assertThat(homepageIcon.mBackgroundColor).isEqualTo(0xff0000); - } - - @Test - public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { - final Context context = spy(RuntimeEnvironment.application); - final View view = LayoutInflater.from(context).inflate(R.layout.dashboard_tile, null); - final DashboardAdapter.DashboardItemHolder holder = - new DashboardAdapter.DashboardItemHolder(view); - final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); - tile.getMetaData().putInt(TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, - R.color.memory_critical); - doReturn(Icon.createWithResource(context, R.drawable.ic_settings)) - .when(tile).getIcon(context); - final IconCache iconCache = new IconCache(context); - mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); - ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); - - doReturn("another.package").when(context).getPackageName(); - mDashboardAdapter.onBindTile(holder, tile); - - final RoundedHomepageIcon homepageIcon = (RoundedHomepageIcon) iconCache.getIcon( - tile.getIcon(context)); - assertThat(homepageIcon.mBackgroundColor) - .isEqualTo(RuntimeEnvironment.application.getColor(R.color.memory_critical)); - } - @Test public void onBindTile_externalTile_usingRoundedHomepageIcon_shouldNotUpdateIcon() { final Context context = RuntimeEnvironment.application; @@ -315,7 +265,7 @@ public class DashboardAdapterTest { any(RoundedHomepageIcon.class)); } - private List makeSuggestionsV2(String... pkgNames) { + private List makeSuggestions(String... pkgNames) { final List suggestions = new ArrayList<>(); for (String pkgName : pkgNames) { final Suggestion suggestion = new Suggestion.Builder(pkgName) diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java index dbaf8fe5b30..22c589c8fd6 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java @@ -291,7 +291,7 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key"); mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_ICON_URI, "content://com.android.settings/tile_icon"); - mImpl.bindIcon(preference, tile); + mImpl.bindIcon(preference, tile, false /* forceRoundedIcon */); assertThat(preference.getIcon()).isNotNull(); } diff --git a/tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java b/tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java index 177dba0083e..042341b24db 100644 --- a/tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java +++ b/tests/robotests/src/com/android/settings/widget/RoundedHomepageIconTest.java @@ -16,21 +16,30 @@ package com.android.settings.widget; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB; +import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_BACKGROUND_HINT; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Icon; import android.graphics.drawable.ShapeDrawable; +import android.os.Bundle; import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.drawer.CategoryKey; +import com.android.settingslib.drawer.Tile; import org.junit.Before; import org.junit.Test; @@ -41,10 +50,15 @@ import org.robolectric.RuntimeEnvironment; public class RoundedHomepageIconTest { private Context mContext; + private ActivityInfo mActivityInfo; @Before public void setUp() { mContext = RuntimeEnvironment.application; + mActivityInfo = new ActivityInfo(); + mActivityInfo.packageName = mContext.getPackageName(); + mActivityInfo.name = "class"; + mActivityInfo.metaData = new Bundle(); } @Test @@ -68,4 +82,34 @@ public class RoundedHomepageIconTest { verify(background).setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP); } + + @Test + public void setBackgroundColor_externalTileWithBackgroundColorRawValue_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB, 0xff0000); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_settings)) + .when(tile).getIcon(mContext); + final RoundedHomepageIcon icon = + new RoundedHomepageIcon(mContext, new ColorDrawable(Color.BLACK)); + + icon.setBackgroundColor(mContext, tile); + assertThat(icon.mBackgroundColor).isEqualTo(0xff0000); + } + + @Test + public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { + final Tile tile = spy(new Tile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); + mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, + R.color.memory_critical); + doReturn(Icon.createWithResource(mContext, R.drawable.ic_settings)) + .when(tile).getIcon(mContext); + + final RoundedHomepageIcon icon = + new RoundedHomepageIcon(mContext, new ColorDrawable(Color.BLACK)); + icon.setBackgroundColor(mContext, tile); + + assertThat(icon.mBackgroundColor) + .isEqualTo(mContext.getColor(R.color.memory_critical)); + } + } From ce1b61a1461eb8f74217942b5d79b11fbb96323f Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Fri, 10 Aug 2018 16:36:46 -0700 Subject: [PATCH 25/25] Move conditional stuff from dashboard/ to homepage package. dashboard package is not a real place for homepage stuff. Putting Conditionals here makes it easier to refactor it for new homepage in the future Bug: 110405144 Bug: 112485407 Test: robotests Change-Id: If433aeac8766124f0f4f6e5786b93ac1372bb745 --- res/layout/dashboard.xml | 2 +- .../settings/dashboard/DashboardAdapter.java | 4 ++-- .../android/settings/dashboard/DashboardData.java | 2 +- .../settings/dashboard/DashboardSummary.java | 10 +++++----- .../settings/fuelgauge/BatterySaverController.java | 14 +++----------- .../conditional/AbnormalRingerConditionBase.java | 2 +- .../conditional/AirplaneModeCondition.java | 2 +- .../conditional/BackgroundDataCondition.java | 2 +- .../conditional/BatterySaverCondition.java | 2 +- .../conditional/CellularDataCondition.java | 2 +- .../conditional/Condition.java | 6 +++--- .../conditional/ConditionAdapter.java | 10 +++++----- .../conditional/ConditionManager.java | 4 ++-- .../conditional/DndCondition.java | 6 +++--- .../conditional/FocusRecyclerView.java | 2 +- .../conditional/HotspotCondition.java | 2 +- .../conditional/NightDisplayCondition.java | 2 +- .../conditional/RingerMutedCondition.java | 2 +- .../conditional/RingerVibrateCondition.java | 2 +- .../conditional/WorkModeCondition.java | 2 +- .../settings/dashboard/DashboardAdapterTest.java | 2 +- .../settings/dashboard/DashboardDataTest.java | 4 ++-- .../settings/dashboard/DashboardSummaryTest.java | 4 ++-- .../fuelgauge/BatterySaverControllerTest.java | 1 - .../AbnormalRingerConditionBaseTest.java | 3 ++- .../conditional/BackgroundDataConditionTest.java | 3 ++- .../conditional/BatterySaverConditionTest.java | 3 ++- .../conditional/ConditionAdapterTest.java | 7 ++++--- .../conditional/ConditionTest.java | 3 ++- .../conditional/DndConditionTest.java | 3 ++- .../conditional/RingerMutedConditionTest.java | 3 ++- .../conditional/RingerVibrateConditionTest.java | 3 ++- .../conditional/WorkModeConditionTest.java | 2 +- 33 files changed, 60 insertions(+), 61 deletions(-) rename src/com/android/settings/{dashboard => homepage}/conditional/AbnormalRingerConditionBase.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/AirplaneModeCondition.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/BackgroundDataCondition.java (97%) rename src/com/android/settings/{dashboard => homepage}/conditional/BatterySaverCondition.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/CellularDataCondition.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/Condition.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/ConditionAdapter.java (99%) rename src/com/android/settings/{dashboard => homepage}/conditional/ConditionManager.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/DndCondition.java (99%) rename src/com/android/settings/{dashboard => homepage}/conditional/FocusRecyclerView.java (96%) rename src/com/android/settings/{dashboard => homepage}/conditional/HotspotCondition.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/NightDisplayCondition.java (98%) rename src/com/android/settings/{dashboard => homepage}/conditional/RingerMutedCondition.java (97%) rename src/com/android/settings/{dashboard => homepage}/conditional/RingerVibrateCondition.java (96%) rename src/com/android/settings/{dashboard => homepage}/conditional/WorkModeCondition.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/AbnormalRingerConditionBaseTest.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/BackgroundDataConditionTest.java (97%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/BatterySaverConditionTest.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/ConditionAdapterTest.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/ConditionTest.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/DndConditionTest.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/RingerMutedConditionTest.java (98%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/RingerVibrateConditionTest.java (97%) rename tests/robotests/src/com/android/settings/{dashboard => homepage}/conditional/WorkModeConditionTest.java (97%) diff --git a/res/layout/dashboard.xml b/res/layout/dashboard.xml index 536c00a7075..ccb50ae10ed 100644 --- a/res/layout/dashboard.xml +++ b/res/layout/dashboard.xml @@ -14,7 +14,7 @@ limitations under the License. --> - { public static final String TAG = "ConditionAdapter"; diff --git a/src/com/android/settings/dashboard/conditional/ConditionManager.java b/src/com/android/settings/homepage/conditional/ConditionManager.java similarity index 98% rename from src/com/android/settings/dashboard/conditional/ConditionManager.java rename to src/com/android/settings/homepage/conditional/ConditionManager.java index 2754d8a5e64..e84b71a3d26 100644 --- a/src/com/android/settings/dashboard/conditional/ConditionManager.java +++ b/src/com/android/settings/homepage/conditional/ConditionManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.content.Context; import android.os.AsyncTask; @@ -44,7 +44,7 @@ public class ConditionManager implements LifecycleObserver, OnResume, OnPause { private static final boolean DEBUG = false; - private static final String PKG = "com.android.settings.dashboard.conditional."; + private static final String PKG = "com.android.settings.homepage.conditional."; private static final String FILE_NAME = "condition_state.xml"; private static final String TAG_CONDITIONS = "cs"; diff --git a/src/com/android/settings/dashboard/conditional/DndCondition.java b/src/com/android/settings/homepage/conditional/DndCondition.java similarity index 99% rename from src/com/android/settings/dashboard/conditional/DndCondition.java rename to src/com/android/settings/homepage/conditional/DndCondition.java index 32184e174fc..3e8052b4808 100644 --- a/src/com/android/settings/dashboard/conditional/DndCondition.java +++ b/src/com/android/settings/homepage/conditional/DndCondition.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.app.NotificationManager; import android.content.BroadcastReceiver; @@ -26,13 +26,13 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; +import androidx.annotation.VisibleForTesting; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.ZenModeSettings; -import androidx.annotation.VisibleForTesting; - public class DndCondition extends Condition { private static final String TAG = "DndCondition"; diff --git a/src/com/android/settings/dashboard/conditional/FocusRecyclerView.java b/src/com/android/settings/homepage/conditional/FocusRecyclerView.java similarity index 96% rename from src/com/android/settings/dashboard/conditional/FocusRecyclerView.java rename to src/com/android/settings/homepage/conditional/FocusRecyclerView.java index abe4743cb3e..78b9eede458 100644 --- a/src/com/android/settings/dashboard/conditional/FocusRecyclerView.java +++ b/src/com/android/settings/homepage/conditional/FocusRecyclerView.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.content.Context; import android.util.AttributeSet; diff --git a/src/com/android/settings/dashboard/conditional/HotspotCondition.java b/src/com/android/settings/homepage/conditional/HotspotCondition.java similarity index 98% rename from src/com/android/settings/dashboard/conditional/HotspotCondition.java rename to src/com/android/settings/homepage/conditional/HotspotCondition.java index 68f2382176e..7212e841701 100644 --- a/src/com/android/settings/dashboard/conditional/HotspotCondition.java +++ b/src/com/android/settings/homepage/conditional/HotspotCondition.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.content.BroadcastReceiver; import android.content.Context; diff --git a/src/com/android/settings/dashboard/conditional/NightDisplayCondition.java b/src/com/android/settings/homepage/conditional/NightDisplayCondition.java similarity index 98% rename from src/com/android/settings/dashboard/conditional/NightDisplayCondition.java rename to src/com/android/settings/homepage/conditional/NightDisplayCondition.java index c3ba534fd83..95769734713 100644 --- a/src/com/android/settings/dashboard/conditional/NightDisplayCondition.java +++ b/src/com/android/settings/homepage/conditional/NightDisplayCondition.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.content.Intent; import android.graphics.drawable.Drawable; diff --git a/src/com/android/settings/dashboard/conditional/RingerMutedCondition.java b/src/com/android/settings/homepage/conditional/RingerMutedCondition.java similarity index 97% rename from src/com/android/settings/dashboard/conditional/RingerMutedCondition.java rename to src/com/android/settings/homepage/conditional/RingerMutedCondition.java index 7f7bc2be090..740e6e48bab 100644 --- a/src/com/android/settings/dashboard/conditional/RingerMutedCondition.java +++ b/src/com/android/settings/homepage/conditional/RingerMutedCondition.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static android.content.Context.NOTIFICATION_SERVICE; diff --git a/src/com/android/settings/dashboard/conditional/RingerVibrateCondition.java b/src/com/android/settings/homepage/conditional/RingerVibrateCondition.java similarity index 96% rename from src/com/android/settings/dashboard/conditional/RingerVibrateCondition.java rename to src/com/android/settings/homepage/conditional/RingerVibrateCondition.java index 6af05c1faed..ea91c0ef28b 100644 --- a/src/com/android/settings/dashboard/conditional/RingerVibrateCondition.java +++ b/src/com/android/settings/homepage/conditional/RingerVibrateCondition.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.graphics.drawable.Drawable; import android.media.AudioManager; diff --git a/src/com/android/settings/dashboard/conditional/WorkModeCondition.java b/src/com/android/settings/homepage/conditional/WorkModeCondition.java similarity index 98% rename from src/com/android/settings/dashboard/conditional/WorkModeCondition.java rename to src/com/android/settings/homepage/conditional/WorkModeCondition.java index 941d5b02b68..9e467faf446 100644 --- a/src/com/android/settings/dashboard/conditional/WorkModeCondition.java +++ b/src/com/android/settings/homepage/conditional/WorkModeCondition.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import android.content.Context; import android.content.Intent; diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java index 8f10af7afec..54e004f9e0d 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java @@ -45,7 +45,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; import com.android.settings.SettingsActivity; -import com.android.settings.dashboard.conditional.Condition; +import com.android.settings.homepage.conditional.Condition; import com.android.settings.dashboard.suggestions.SuggestionAdapter; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java index dfa049446b7..d92cceb62a8 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardDataTest.java @@ -33,8 +33,8 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListUpdateCallback; -import com.android.settings.dashboard.conditional.AirplaneModeCondition; -import com.android.settings.dashboard.conditional.Condition; +import com.android.settings.homepage.conditional.AirplaneModeCondition; +import com.android.settings.homepage.conditional.Condition; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.DashboardCategory; diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java index d93cd810caa..4eaeaf23e10 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java @@ -30,8 +30,8 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; -import com.android.settings.dashboard.conditional.ConditionManager; -import com.android.settings.dashboard.conditional.FocusRecyclerView; +import com.android.settings.homepage.conditional.ConditionManager; +import com.android.settings.homepage.conditional.FocusRecyclerView; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.drawer.CategoryKey; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatterySaverControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatterySaverControllerTest.java index ad5537cf2e2..ac93941a7d0 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatterySaverControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatterySaverControllerTest.java @@ -56,7 +56,6 @@ public class BatterySaverControllerTest { mBatterySaverController = spy(new BatterySaverController(mContext)); ReflectionHelpers.setField(mBatterySaverController, "mPowerManager", mPowerManager); ReflectionHelpers.setField(mBatterySaverController, "mBatterySaverPref", mBatterySaverPref); - doNothing().when(mBatterySaverController).refreshConditionManager(); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/AbnormalRingerConditionBaseTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/AbnormalRingerConditionBaseTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/conditional/AbnormalRingerConditionBaseTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/AbnormalRingerConditionBaseTest.java index c72131fa8e2..efc5ceab889 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/AbnormalRingerConditionBaseTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/AbnormalRingerConditionBaseTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.when; import android.content.Context; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/BackgroundDataConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionTest.java similarity index 97% rename from tests/robotests/src/com/android/settings/dashboard/conditional/BackgroundDataConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionTest.java index 725be7962d4..289fa7c22ba 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/BackgroundDataConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/BackgroundDataConditionTest.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/BatterySaverConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/BatterySaverConditionTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/conditional/BatterySaverConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/BatterySaverConditionTest.java index fa05ae030f6..323e6c5ff91 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/BatterySaverConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/BatterySaverConditionTest.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/ConditionAdapterTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/ConditionAdapterTest.java index 62d1dfaa6af..663c630f3db 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionAdapterTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/ConditionAdapterTest.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -24,6 +25,8 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; +import androidx.recyclerview.widget.RecyclerView; + import com.android.settings.R; import com.android.settings.dashboard.DashboardAdapter; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -38,8 +41,6 @@ import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.List; -import androidx.recyclerview.widget.RecyclerView; - @RunWith(SettingsRobolectricTestRunner.class) public class ConditionAdapterTest { diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/ConditionTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/ConditionTest.java index 6d5673151a2..981ef662ef5 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/ConditionTest.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/DndConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/DndConditionTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/conditional/DndConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/DndConditionTest.java index 29ad60f713b..9da1f9c3bb5 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/DndConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/DndConditionTest.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/RingerMutedConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/RingerMutedConditionTest.java similarity index 98% rename from tests/robotests/src/com/android/settings/dashboard/conditional/RingerMutedConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/RingerMutedConditionTest.java index 66ef5a039b3..408aa137891 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/RingerMutedConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/RingerMutedConditionTest.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/RingerVibrateConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/RingerVibrateConditionTest.java similarity index 97% rename from tests/robotests/src/com/android/settings/dashboard/conditional/RingerVibrateConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/RingerVibrateConditionTest.java index a134fac5721..e886236e209 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/RingerVibrateConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/RingerVibrateConditionTest.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.when; import android.content.Context; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/WorkModeConditionTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionTest.java similarity index 97% rename from tests/robotests/src/com/android/settings/dashboard/conditional/WorkModeConditionTest.java rename to tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionTest.java index 8ba6ecc3188..c846be0f854 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/WorkModeConditionTest.java +++ b/tests/robotests/src/com/android/settings/homepage/conditional/WorkModeConditionTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License */ -package com.android.settings.dashboard.conditional; +package com.android.settings.homepage.conditional; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.spy;