From e72fb487f23b9377e4b258395cd471462be6a3df Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 1 Nov 2017 12:54:29 -0700 Subject: [PATCH 01/28] Add screen resource for Colors setting. - each preference fragment should have its own preference screen layout in order to show the title correctly. Adding the missing data for ColorModePreferenceFragment. Change-Id: I172429dc957f94351456d4bc829897a578d7dbf9 Fixes: 68763217 Test: make RunSettingsRoboTests (cherry picked from commit 5a66ddbd6f399e5413bc058ce12cb1646e4e3903) --- res/xml/color_mode_settings.xml | 21 ++++++++++++++++ .../display/ColorModePreferenceFragment.java | 5 ++++ .../widget/RadioButtonPickerFragment.java | 3 +++ .../DefaultAppPickerFragmentTest.java | 5 ++++ .../ColorModePreferenceFragmentTest.java | 24 ++++++++++++++++--- .../widget/RadioButtonPickerFragmentTest.java | 5 ++++ 6 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 res/xml/color_mode_settings.xml diff --git a/res/xml/color_mode_settings.xml b/res/xml/color_mode_settings.xml new file mode 100644 index 00000000000..b7f58d2ea08 --- /dev/null +++ b/res/xml/color_mode_settings.xml @@ -0,0 +1,21 @@ + + + + diff --git a/src/com/android/settings/display/ColorModePreferenceFragment.java b/src/com/android/settings/display/ColorModePreferenceFragment.java index 07cf82e7901..9f18fd8423b 100644 --- a/src/com/android/settings/display/ColorModePreferenceFragment.java +++ b/src/com/android/settings/display/ColorModePreferenceFragment.java @@ -44,6 +44,11 @@ public void onAttach(Context context) { mController = new NightDisplayController(context); } + @Override + protected int getPreferenceScreenResId() { + return R.xml.color_mode_settings; + } + @Override protected List getCandidates() { Context c = getContext(); diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java index 7489a772c5c..c7689090959 100644 --- a/src/com/android/settings/widget/RadioButtonPickerFragment.java +++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java @@ -82,6 +82,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, return view; } + @Override + protected abstract int getPreferenceScreenResId(); + @Override public void onRadioButtonClicked(RadioButtonPreference selected) { final String selectedKey = selected.getKey(); diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java index 3621edd3645..42a62254a0a 100644 --- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java +++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java @@ -114,6 +114,11 @@ public int getMetricsCategory() { return 0; } + @Override + protected int getPreferenceScreenResId() { + return 0; + } + @Override protected List getCandidates() { return new ArrayList<>(); diff --git a/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java index 9ee79ffe545..fb9bb9f490b 100644 --- a/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java @@ -16,11 +16,18 @@ package com.android.settings.display; import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.os.Bundle; + import com.android.internal.app.NightDisplayController; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; @@ -111,20 +118,31 @@ public void getKey_saturated() { @Test public void setKey_natural() { mFragment.setDefaultKey(ColorModePreferenceFragment.KEY_COLOR_MODE_NATURAL); - Mockito.verify(mController).setColorMode(NightDisplayController.COLOR_MODE_NATURAL); + verify(mController).setColorMode(NightDisplayController.COLOR_MODE_NATURAL); } @Config(shadows = {SettingsShadowSystemProperties.class}) @Test public void setKey_boosted() { mFragment.setDefaultKey(ColorModePreferenceFragment.KEY_COLOR_MODE_BOOSTED); - Mockito.verify(mController).setColorMode(NightDisplayController.COLOR_MODE_BOOSTED); + verify(mController).setColorMode(NightDisplayController.COLOR_MODE_BOOSTED); } @Config(shadows = {SettingsShadowSystemProperties.class}) @Test public void setKey_saturated() { mFragment.setDefaultKey(ColorModePreferenceFragment.KEY_COLOR_MODE_SATURATED); - Mockito.verify(mController).setColorMode(NightDisplayController.COLOR_MODE_SATURATED); + verify(mController).setColorMode(NightDisplayController.COLOR_MODE_SATURATED); + } + + @Test + public void onCreatePreferences_useNewTitle_shouldAddColorModePreferences() { + doNothing().when(mFragment).addPreferencesFromResource(anyInt()); + doNothing().when(mFragment).updateCandidates(); + + mFragment.onCreatePreferences(Bundle.EMPTY, null /* rootKey */); + + verify(mFragment).addPreferencesFromResource(R.xml.color_mode_settings); } + } diff --git a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java index 40d73eb327a..8ca68ae50b7 100644 --- a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java +++ b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java @@ -113,6 +113,11 @@ public int getMetricsCategory() { return 0; } + @Override + protected int getPreferenceScreenResId() { + return 0; + } + @Override protected List getCandidates() { return new ArrayList<>(); From e33bbc6c7ac45f96f1b4a6c310386102f5a1e4a0 Mon Sep 17 00:00:00 2001 From: Matthew Fritze Date: Thu, 9 Nov 2017 09:40:09 -0800 Subject: [PATCH 02/28] Prevent crash in Search from Stack overflow NotificationSettingBase's getPreferenceController call is recursive with no base case which crashes search. Root cause here is that NotificationSettingsBase should not be indexed for seacrh but was probably failing the code inspection test. Added NotificationSettingsBase child classes to grandfather list of classes that should be ignored for search. Change-Id: I04ed242db3a394b88e0d0ac420aaaff6f9301cb5 Fixes: 69096424 Test: robotests (cherry picked from commit f6cf598d0325c3c96927285b22c84f042e95b6fb) --- .../ChannelImportanceSettings.java | 11 ----- .../NotificationSettingsBase.java | 40 +++++++------------ .../search/SearchIndexableResources.java | 9 ----- ...randfather_not_implementing_index_provider | 6 ++- .../grandfather_not_implementing_indexable | 2 +- 5 files changed, 20 insertions(+), 48 deletions(-) diff --git a/src/com/android/settings/notification/ChannelImportanceSettings.java b/src/com/android/settings/notification/ChannelImportanceSettings.java index bffed08079c..27b23b8c2cc 100644 --- a/src/com/android/settings/notification/ChannelImportanceSettings.java +++ b/src/com/android/settings/notification/ChannelImportanceSettings.java @@ -163,15 +163,4 @@ public void onRadioButtonClicked(RadioButtonPreference clicked) { mChannel.lockFields(USER_LOCKED_IMPORTANCE); mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel); } - - // This page exists per notification channel; should not be included - // in search - public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getXmlResourcesToIndex( - Context context, boolean enabled) { - return null; - } - }; } diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java index e9da5d96f32..9afb61845ee 100644 --- a/src/com/android/settings/notification/NotificationSettingsBase.java +++ b/src/com/android/settings/notification/NotificationSettingsBase.java @@ -208,15 +208,19 @@ protected void collectConfigActivities() { intent, 0 //PackageManager.MATCH_DEFAULT_ONLY ); - if (DEBUG) Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" - + (resolveInfos.size() == 0 ? " ;_;" : "")); + if (DEBUG) { + Log.d(TAG, "Found " + resolveInfos.size() + " preference activities" + + (resolveInfos.size() == 0 ? " ;_;" : "")); + } for (ResolveInfo ri : resolveInfos) { final ActivityInfo activityInfo = ri.activityInfo; final ApplicationInfo appInfo = activityInfo.applicationInfo; if (mAppRow.settingsIntent != null) { - if (DEBUG) Log.v(TAG, "Ignoring duplicate notification preference activity (" - + activityInfo.name + ") for package " - + activityInfo.packageName); + if (DEBUG) { + Log.d(TAG, "Ignoring duplicate notification preference activity (" + + activityInfo.name + ") for package " + + activityInfo.packageName); + } continue; } mAppRow.settingsIntent = intent @@ -280,7 +284,7 @@ && isChannelBlockable(channel) public boolean onPreferenceChange(Preference preference, Object o) { boolean value = (Boolean) o; - int importance = value ? IMPORTANCE_LOW : IMPORTANCE_NONE; + int importance = value ? IMPORTANCE_LOW : IMPORTANCE_NONE; channel.setImportance(importance); channel.lockFields( NotificationChannel.USER_LOCKED_IMPORTANCE); @@ -365,8 +369,10 @@ protected void onPackageRemoved() { public void onReceive(Context context, Intent intent) { String packageName = intent.getData().getSchemeSpecificPart(); if (mPkgInfo == null || TextUtils.equals(mPkgInfo.packageName, packageName)) { - if (DEBUG) Log.d(TAG, "Package (" + packageName + ") removed. Removing" - + "NotificationSettingsBase."); + if (DEBUG) { + Log.d(TAG, "Package (" + packageName + ") removed. Removing" + + "NotificationSettingsBase."); + } onPackageRemoved(); } } @@ -380,24 +386,6 @@ public void onReceive(Context context, Intent intent) { return left.getId().compareTo(right.getId()); }; - /** - * These screens aren't searchable - they only make sense in the context of an app, so - * surfacing a generic version would be impossible. - */ - public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getXmlResourcesToIndex(Context context, - boolean enabled) { - return new ArrayList<>(); - } - - @Override - public List getPreferenceControllers(Context context) { - return getPreferenceControllers(context); - } - }; - protected class ImportanceListener { protected void onImportanceChanged() { final PreferenceScreen screen = getPreferenceScreen(); diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java index 4548342ad57..c5243460eb3 100644 --- a/src/com/android/settings/search/SearchIndexableResources.java +++ b/src/com/android/settings/search/SearchIndexableResources.java @@ -64,10 +64,6 @@ import com.android.settings.location.ScanningSettings; import com.android.settings.network.NetworkDashboardFragment; import com.android.settings.nfc.PaymentSettings; -import com.android.settings.notification.AppNotificationSettings; -import com.android.settings.notification.ChannelGroupNotificationSettings; -import com.android.settings.notification.ChannelImportanceSettings; -import com.android.settings.notification.ChannelNotificationSettings; import com.android.settings.notification.ConfigureNotificationSettings; import com.android.settings.notification.SoundSettings; import com.android.settings.notification.ZenModeAutomationSettings; @@ -161,7 +157,6 @@ static void addIndex(Class indexClass) { addIndex(TtsEnginePreferenceFragment.class); addIndex(MagnificationPreferenceFragment.class); addIndex(AccessibilityShortcutPreferenceFragment.class); - addIndex(ChannelImportanceSettings.class); addIndex(DreamSettings.class); addIndex(SupportDashboardActivity.class); addIndex(AutomaticStorageManagerSettings.class); @@ -171,10 +166,6 @@ static void addIndex(Class indexClass) { addIndex(LockscreenDashboardFragment.class); addIndex(ZenModeBehaviorSettings.class); addIndex(ZenModeAutomationSettings.class); - addIndex(AppNotificationSettings.class); - addIndex(ChannelNotificationSettings.class); - addIndex(ChannelImportanceSettings.class); - addIndex(ChannelGroupNotificationSettings.class); } private SearchIndexableResources() { diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index fda5c8a5c70..6704fc3fb0e 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -7,6 +7,10 @@ com.android.settings.fuelgauge.AdvancedPowerUsageDetail com.android.settings.development.featureflags.FeatureFlagsDashboard com.android.settings.development.qstile.DevelopmentTileConfigFragment com.android.settings.deviceinfo.StorageProfileFragment +com.android.settings.notification.ChannelNotificationSettings +com.android.settings.notification.ChannelImportanceSettings +com.android.settings.notification.ChannelGroupNotificationSettings +com.android.settings.notification.AppNotificationSettings com.android.settings.wifi.details.WifiNetworkDetailsFragment com.android.settings.wifi.p2p.WifiP2pSettings com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionCamera @@ -15,4 +19,4 @@ com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionMi com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages com.android.settings.enterprise.EnterpriseSetDefaultAppsListFragment com.android.settings.wifi.tether.WifiTetherSettings -com.android.settings.wifi.SavedAccessPointsWifiSettings +com.android.settings.wifi.SavedAccessPointsWifiSettings \ No newline at end of file diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index 7c82e78d162..c2a084c5360 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -79,4 +79,4 @@ com.android.settings.IccLockSettings com.android.settings.TetherSettings com.android.settings.ApnEditor com.android.settings.UserCredentialsSettings -com.android.settings.TestingSettings +com.android.settings.TestingSettings \ No newline at end of file From d7f6f40d92ca94a7bbfab7e2b732d88ab1fd7dd8 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 13 Nov 2017 18:30:11 +0000 Subject: [PATCH 03/28] Revert "Expand all preferences when launch from search." This reverts commit 6589a07d5915d8bcc911ddf36054549a7cc1a219. Bug: 68988454 Fixes: 69242304 Change-Id: Iec29dbe302064a6209fbb0a8e7901c9a0f8744c0 (cherry picked from commit 9ce35ad0231d27292e6df5ebbd078947626dd3df) --- .../settings/SettingsPreferenceFragment.java | 16 ++++--------- .../SettingsPreferenceFragmentTest.java | 24 ++++--------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index f265f9892f8..a3d26af8eb7 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -150,8 +150,8 @@ public void onCreate(Bundle icicle) { } // Prepare help url and enable menu if necessary - final Bundle arguments = getArguments(); - final int helpResource; + Bundle arguments = getArguments(); + int helpResource; if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) { helpResource = arguments.getInt(HELP_URI_RESOURCE_KEY); } else { @@ -160,14 +160,6 @@ public void onCreate(Bundle icicle) { if (helpResource != 0) { mHelpUri = getResources().getString(helpResource); } - - // Check if we should keep the preferences expanded. - if (arguments != null) { - mPreferenceKey = arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); - if (!TextUtils.isEmpty(mPreferenceKey)) { - getPreferenceScreen().setInitialExpandedChildrenCount(Integer.MAX_VALUE); - } - } } @Override @@ -232,7 +224,9 @@ public void onActivityCreated(Bundle savedInstanceState) { public void onResume() { super.onResume(); - if (mPreferenceKey != null) { + final Bundle args = getArguments(); + if (args != null) { + mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); highlightPreferenceIfNeeded(); } } diff --git a/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java index aa92ebb4975..dc4166d7eb3 100644 --- a/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/SettingsPreferenceFragmentTest.java @@ -16,9 +16,9 @@ package com.android.settings; + import android.app.Activity; import android.content.Context; -import android.os.Bundle; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceManager; @@ -39,18 +39,18 @@ 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 com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.testutils.shadow.SettingsShadowResources; @RunWith(SettingsRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION_O) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class SettingsPreferenceFragmentTest { private static final int ITEM_COUNT = 5; + @Mock + private PreferenceManager mPreferenceManager; @Mock private Activity mActivity; @Mock @@ -142,21 +142,6 @@ public void testUpdateEmptyView_containerNull_emptyViewGone() { assertThat(mEmptyView.getVisibility()).isEqualTo(View.GONE); } - @Test - @Config(shadows = SettingsShadowResources.SettingsShadowTheme.class) - public void onCreate_hasExtraFragmentKey_shouldExpandPreferences() { - doReturn(mContext.getTheme()).when(mActivity).getTheme(); - doReturn(mContext.getResources()).when(mFragment).getResources(); - doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); - final Bundle bundle = new Bundle(); - bundle.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, "test_key"); - doReturn(bundle).when(mFragment).getArguments(); - - mFragment.onCreate(null /* icicle */); - - verify(mPreferenceScreen).setInitialExpandedChildrenCount(Integer.MAX_VALUE); - } - public static class TestFragment extends SettingsPreferenceFragment { @Override @@ -165,4 +150,5 @@ public int getMetricsCategory() { } } + } From 2e1009f4a3fae112de4be10f8d036e7a11026d97 Mon Sep 17 00:00:00 2001 From: Tony Mantler Date: Wed, 15 Nov 2017 11:19:40 -0800 Subject: [PATCH 04/28] Copy proguard rules needed for Lifecycle support Bug: 69350851 Test: Manual, sadly Change-Id: Ice2b2697ec269e3ed0d3ca81940948189357cefb (cherry picked from commit bd6fcdbd2b4c0f4eb87fa8d2072ff809d2fbb3b5) --- proguard.flags | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/proguard.flags b/proguard.flags index d644f47887c..7a403a4490c 100644 --- a/proguard.flags +++ b/proguard.flags @@ -39,3 +39,21 @@ public static ** SEARCH_INDEX_DATA_PROVIDER; public static ** SUMMARY_PROVIDER_FACTORY; } + +# Keep classes, annotations and members used by Lifecycle +-keepattributes *Annotation* + +-keepclassmembers enum android.arch.lifecycle.Lifecycle$Event { + ; +} + +-keep class * implements android.arch.lifecycle.LifecycleObserver { +} + +-keep class * implements android.arch.lifecycle.GeneratedAdapter { + (...); +} + +-keepclassmembers class ** { + @android.arch.lifecycle.OnLifecycleEvent *; +} From bbc79a2e1e723a6e78e9643e00b068ddf688a25c Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Mon, 11 Dec 2017 11:18:10 -0800 Subject: [PATCH 05/28] Update package name for PictureAndPictureSettings - the settings have been moved into the appinfo package, but the path has not been updated properly in the android manifest. Change-Id: I3a00a187bd2fdbeb926e2bb8cc1c4ab720ccd72a Fixes: 70491786 Test: manual (cherry picked from commit ebf884384073e96754d6bea5ee6e611f293a65f5) --- AndroidManifest.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8eaf761d478..6ccb8d1b08d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2573,7 +2573,7 @@ + android:value="com.android.settings.applications.appinfo.PictureInPictureSettings" /> + android:value="com.android.settings.applications.appinfo.PictureInPictureDetails" /> + android:value="com.android.settings.applications.appinfo.DrawOverlayDetails" /> + android:value="com.android.settings.applications.appinfo.WriteSettingsDetails" /> + android:value="com.android.settings.applications.appinfo.ExternalSourcesDetails" /> Date: Mon, 11 Dec 2017 13:45:40 -0800 Subject: [PATCH 06/28] Update package name for PictureAndPictureSettings - also need to update the reference in the special app access xml page. Change-Id: I1199f70adf18d3f0e21a946848239526d9c8b3c8 Fixes: 70491786 Test: make SettingsUnitTests (cherry picked from commit a8472007a7d195410c030146361122de63a07e6a) --- res/xml/special_access.xml | 2 +- .../SpecialAppAccessSettingsTest.java | 89 +++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tests/unit/src/com/android/settings/applications/SpecialAppAccessSettingsTest.java diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml index 6adfc933fb4..829bc53c6c1 100644 --- a/res/xml/special_access.xml +++ b/res/xml/special_access.xml @@ -68,7 +68,7 @@ Date: Wed, 20 Dec 2017 16:38:45 -0800 Subject: [PATCH 07/28] Catch NullPointerException in PreIndexDataCollector. Null pointer exception is thrown from ContentProviderNative when we query the non indexable keys. Added a try-catch block to prevent it from crashing. Change-Id: I45c1e34bb81a4739ae2eb5dd19a08781ab7beeb1 Fix: 70900076 Test: manual (cherry picked from commit 559ac8a72a6ee6265199d90baa2f99b308cd0844) --- .../settings/search/indexing/PreIndexDataCollector.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/search/indexing/PreIndexDataCollector.java b/src/com/android/settings/search/indexing/PreIndexDataCollector.java index a4e11317f23..63000b48744 100644 --- a/src/com/android/settings/search/indexing/PreIndexDataCollector.java +++ b/src/com/android/settings/search/indexing/PreIndexDataCollector.java @@ -287,8 +287,14 @@ private List getNonIndexablesKeys(Context packageContext, Uri uri, String[] projection) { final ContentResolver resolver = packageContext.getContentResolver(); - final Cursor cursor = resolver.query(uri, projection, null, null, null); final List result = new ArrayList<>(); + Cursor cursor; + try { + cursor = resolver.query(uri, projection, null, null, null); + } catch (NullPointerException e) { + Log.e(TAG, "Exception querying the keys!", e); + return result; + } if (cursor == null) { Log.w(TAG, "Cannot add index data for Uri: " + uri.toString()); From 59c2323bb7300845c695a4d1c1629c4c864ad4de Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Thu, 4 Jan 2018 13:05:46 -0800 Subject: [PATCH 08/28] Disable animation when adjusting font size. - The fade-in and fade-out animation for transitioning between different frames in the text preview is very janky. Disable the animation to get a smoother transition while dragging along the seek bar. Change-Id: I201faf8229961ea5fa5fe42efd312290a4af0f30 Fixes: 71569578 Test: visual (cherry picked from commit 121b3579afc02f54862c2feb99a01c88343bde53) --- src/com/android/settings/PreviewSeekBarPreferenceFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java index f5f3017af79..026032d26c6 100644 --- a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java +++ b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java @@ -67,7 +67,7 @@ private class onPreviewSeekBarChangeListener implements OnSeekBarChangeListener @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - setPreviewLayer(progress, true); + setPreviewLayer(progress, false); if (!mSeekByTouch) { commit(); } From df27c89f7e82c7c5fa6854185c86e4db9a66f6d7 Mon Sep 17 00:00:00 2001 From: Leif Wilden Date: Mon, 22 Jan 2018 20:22:02 +0000 Subject: [PATCH 09/28] Revert "Migrate to use instrumentation classes from settingslib." This reverts commit 1546cca529bf56430332e15b05ceb6efb37e57bf. Reason for revert: Broke fingerprint setup flow. b/72267201 Change-Id: I8321265ae64732c526325882ddea51080decddf5 (cherry picked from commit cab0ee611d375d70a3803d857e62c745ccd98de2) --- .../android/settings/AirplaneModeEnabler.java | 2 +- .../android/settings/DeviceAdminSettings.java | 16 +- .../android/settings/SettingsActivity.java | 12 +- .../settings/SettingsPreferenceFragment.java | 2 +- src/com/android/settings/Utils.java | 3 +- .../accounts/AccountPreferenceController.java | 2 +- .../applications/UsageAccessDetails.java | 2 +- .../bluetooth/BluetoothDevicePreference.java | 2 +- ...toothDeviceRenamePreferenceController.java | 2 +- .../settings/bluetooth/BluetoothEnabler.java | 2 +- .../BluetoothFilesPreferenceController.java | 2 +- .../settings/core/InstrumentedActivity.java | 8 +- .../settings/core/InstrumentedFragment.java | 20 +- .../core/InstrumentedPreferenceFragment.java | 20 +- .../core/instrumentation/EventLogWriter.java | 110 ++++++++ .../core/instrumentation/Instrumentable.java | 28 ++ .../InstrumentedDialogFragment.java | 11 +- .../core/instrumentation/LogWriter.java | 84 ++++++ .../MetricsFeatureProvider.java | 159 +++++++++++ .../SharedPreferencesLogger.java | 259 ++++++++++++++++++ .../VisibilityLoggerMixin.java | 100 +++++++ .../settings/dashboard/DashboardAdapter.java | 2 +- .../dashboard/DashboardAdapterV2.java | 2 +- .../DashboardFeatureProviderImpl.java | 8 +- .../dashboard/conditional/Condition.java | 2 +- .../conditional/ConditionAdapter.java | 2 +- .../conditional/ConditionAdapterV2.java | 2 +- .../suggestions/SuggestionAdapter.java | 2 +- .../suggestions/SuggestionAdapterV2.java | 2 +- .../SuggestionFeatureProviderImpl.java | 2 +- .../settings/datausage/DataSaverBackend.java | 2 +- .../android/settings/datetime/ZonePicker.java | 21 +- ...aticStorageManagerSwitchBarController.java | 4 +- .../BuildNumberPreferenceController.java | 2 +- ...eManagementSwitchPreferenceController.java | 2 +- .../StorageItemPreferenceController.java | 2 +- ...playNotificationsPreferenceController.java | 2 +- .../display/AmbientDisplaySettings.java | 2 +- .../AutoRotatePreferenceController.java | 2 +- .../display/ThemePreferenceController.java | 2 +- .../AppButtonsPreferenceController.java | 2 +- .../BatteryAppListPreferenceController.java | 2 +- .../settings/fuelgauge/PowerUsageSummary.java | 2 +- .../fuelgauge/PowerUsageSummaryLegacy.java | 2 +- .../fuelgauge/anomaly/AnomalyUtils.java | 2 +- .../anomaly/action/AnomalyAction.java | 2 +- .../batterytip/actions/BatteryTipAction.java | 2 +- .../actions/SmartBatteryAction.java | 2 +- .../inputmethod/UserDictionarySettings.java | 17 +- .../AirplaneModePreferenceController.java | 2 +- .../network/NetworkDashboardFragment.java | 2 +- .../AbstractZenModePreferenceController.java | 2 +- .../notification/ZenRulePreference.java | 4 +- .../settings/overlay/FeatureFactory.java | 2 +- .../settings/overlay/FeatureFactoryImpl.java | 2 +- .../android/settings/widget/SwitchBar.java | 2 +- .../android/settings/wifi/WifiEnabler.java | 2 +- .../WifiMasterSwitchPreferenceController.java | 2 +- .../WifiDetailPreferenceController.java | 2 +- .../settings/SettingsDialogFragmentTest.java | 7 +- .../InstantAppButtonsControllerTest.java | 2 +- .../BluetoothDevicePreferenceTest.java | 2 +- .../bluetooth/BluetoothEnablerTest.java | 2 +- .../android/settings/bluetooth/UtilsTest.java | 2 +- .../InstrumentableFragmentCodeInspector.java | 1 - .../InstrumentedDialogFragmentTest.java | 1 - .../MetricsFeatureProviderTest.java | 66 ++++- .../SharedPreferenceLoggerTest.java | 185 +++++++++++++ .../VisibilityLoggerMixinTest.java | 123 +++++++++ .../DashboardFeatureProviderImplTest.java | 3 +- .../dashboard/DashboardFragmentTest.java | 2 +- .../dashboard/conditional/ConditionTest.java | 2 +- .../settings/datausage/DataUsageListTest.java | 1 - .../settings/datetime/ZonePickerTest.java | 2 +- ...StorageManagerSwitchBarControllerTest.java | 2 +- ...agementSwitchPreferenceControllerTest.java | 2 +- .../StorageItemPreferenceControllerTest.java | 2 +- ...eSummaryDonutPreferenceControllerTest.java | 2 +- ...NotificationsPreferenceControllerTest.java | 2 +- .../FingerprintEnrollEnrollingTest.java | 4 - .../FingerprintEnrollFindSensorTest.java | 4 - .../FingerprintSuggestionActivityTest.java | 4 - .../SetupFingerprintEnrollFindSensorTest.java | 4 - ...etupFingerprintEnrollIntroductionTest.java | 4 - .../fuelgauge/anomaly/AnomalyUtilsTest.java | 2 +- .../localepicker/LocaleListEditorTest.java | 10 - .../testutils/FakeFeatureFactory.java | 2 +- .../shadow/ShadowEventLogWriter.java | 2 +- .../webview/WebViewAppPickerTest.java | 2 +- .../settings/wifi/WifiEnablerTest.java | 2 +- ...iMasterSwitchPreferenceControllerTest.java | 2 +- .../WifiDetailPreferenceControllerTest.java | 2 +- 92 files changed, 1247 insertions(+), 174 deletions(-) create mode 100644 src/com/android/settings/core/instrumentation/EventLogWriter.java create mode 100644 src/com/android/settings/core/instrumentation/Instrumentable.java create mode 100644 src/com/android/settings/core/instrumentation/LogWriter.java create mode 100644 src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java create mode 100644 src/com/android/settings/core/instrumentation/SharedPreferencesLogger.java create mode 100644 src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java create mode 100644 tests/robotests/src/com/android/settings/core/instrumentation/SharedPreferenceLoggerTest.java create mode 100644 tests/robotests/src/com/android/settings/core/instrumentation/VisibilityLoggerMixinTest.java diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java index 5f93589c92d..4fc205d263d 100644 --- a/src/com/android/settings/AirplaneModeEnabler.java +++ b/src/com/android/settings/AirplaneModeEnabler.java @@ -30,8 +30,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.PhoneStateIntentReceiver; import com.android.internal.telephony.TelephonyProperties; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.WirelessUtils; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListener { diff --git a/src/com/android/settings/DeviceAdminSettings.java b/src/com/android/settings/DeviceAdminSettings.java index bb53018dfd6..9391439267d 100644 --- a/src/com/android/settings/DeviceAdminSettings.java +++ b/src/com/android/settings/DeviceAdminSettings.java @@ -49,9 +49,8 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto; -import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; +import com.android.settings.core.instrumentation.Instrumentable; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import org.xmlpull.v1.XmlPullParserException; @@ -64,7 +63,8 @@ public class DeviceAdminSettings extends ListFragment implements Instrumentable { static final String TAG = "DeviceAdminSettings"; - private VisibilityLoggerMixin mVisibilityLoggerMixin; + private final VisibilityLoggerMixin mVisibilityLoggerMixin = + new VisibilityLoggerMixin(getMetricsCategory()); private DevicePolicyManager mDPM; private UserManager mUm; @@ -85,6 +85,12 @@ public int compareTo(DeviceAdminListItem other) { } } + @Override + public void onAttach(Context context) { + super.onAttach(context); + mVisibilityLoggerMixin.onAttach(context); + } + /** * Internal collection of device admin info objects for all profiles associated with the current * user. @@ -115,8 +121,6 @@ public int getMetricsCategory() { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), - FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()); } @Override diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 5cb7c06c21a..d3580d18b86 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -56,13 +56,13 @@ import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.backup.BackupSettingsActivity; import com.android.settings.core.gateway.SettingsGateway; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.core.instrumentation.SharedPreferencesLogger; import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.DashboardSummary; import com.android.settings.overlay.FeatureFactory; import com.android.settings.wfd.WifiDisplaySettings; import com.android.settings.widget.SwitchBar; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.instrumentation.SharedPreferencesLogger; import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.SettingsDrawerActivity; @@ -92,6 +92,11 @@ public class SettingsActivity extends SettingsDrawerActivity */ public static final String EXTRA_SHOW_FRAGMENT = ":settings:show_fragment"; + /** + * The metrics category constant for logging source when a setting fragment is opened. + */ + public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics"; + /** * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, * this extra can also be specified to supply a Bundle of arguments to pass @@ -215,8 +220,7 @@ public boolean onPreferenceTreeClick(Preference preference) { @Override public SharedPreferences getSharedPreferences(String name, int mode) { if (name.equals(getPackageName() + "_preferences")) { - return new SharedPreferencesLogger(this, getMetricsTag(), - FeatureFactory.getFactory(this).getMetricsFeatureProvider()); + return new SharedPreferencesLogger(this, getMetricsTag()); } return super.getSharedPreferences(name, mode); } diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index c5d477aad0b..d9e264bf33d 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -45,6 +45,7 @@ import com.android.settings.applications.LayoutPreference; import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.instrumentation.Instrumentable; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.search.actionbar.SearchMenuController; import com.android.settings.support.actionbar.HelpMenuController; @@ -52,7 +53,6 @@ import com.android.settings.widget.LoadingViewController; import com.android.settingslib.CustomDialogPreference; import com.android.settingslib.CustomEditTextPreference; -import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.widget.FooterPreferenceMixin; import java.util.UUID; diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 1c674b68f15..ad951216781 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -110,7 +110,6 @@ import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settings.wrapper.FingerprintManagerWrapper; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import java.net.InetAddress; import java.util.ArrayList; @@ -577,7 +576,7 @@ public static Intent onBuildStartFragmentIntent(Context context, String fragment intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut); - intent.putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, sourceMetricsCategory); + intent.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, sourceMetricsCategory); return intent; } diff --git a/src/com/android/settings/accounts/AccountPreferenceController.java b/src/com/android/settings/accounts/AccountPreferenceController.java index 6127ab9bccc..c0bf7d21ef9 100644 --- a/src/com/android/settings/accounts/AccountPreferenceController.java +++ b/src/com/android/settings/accounts/AccountPreferenceController.java @@ -51,12 +51,12 @@ import com.android.settings.SettingsPreferenceFragment; import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.SearchIndexableRaw; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.accounts.AuthenticatorHelper; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java index c172137664b..c10fb55c9ec 100644 --- a/src/com/android/settings/applications/UsageAccessDetails.java +++ b/src/com/android/settings/applications/UsageAccessDetails.java @@ -37,8 +37,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.applications.AppStateUsageBridge.UsageState; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener, OnPreferenceClickListener { diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java index 3fd7ced3e03..a0ce73386a5 100644 --- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java @@ -33,10 +33,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.GearPreference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java index cea0147cecf..a12d1a8ecb2 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java @@ -23,9 +23,9 @@ import android.text.TextUtils; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; public class BluetoothDeviceRenamePreferenceController extends diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index 0f294bd5748..87fa43d2db2 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -27,12 +27,12 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.SwitchWidgetController; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.WirelessUtils; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; /** * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox diff --git a/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java index 1ecfed42515..450c7b2320a 100644 --- a/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java +++ b/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java @@ -23,9 +23,9 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; /** * Controller that shows received files diff --git a/src/com/android/settings/core/InstrumentedActivity.java b/src/com/android/settings/core/InstrumentedActivity.java index 294de2cb957..9b24756b19e 100644 --- a/src/com/android/settings/core/InstrumentedActivity.java +++ b/src/com/android/settings/core/InstrumentedActivity.java @@ -16,9 +16,8 @@ package com.android.settings.core; -import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; +import com.android.settings.core.instrumentation.Instrumentable; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.core.lifecycle.ObservableActivity; /** @@ -28,8 +27,7 @@ public abstract class InstrumentedActivity extends ObservableActivity implements public InstrumentedActivity() { // Mixin that logs visibility change for activity. - getLifecycle().addObserver(new VisibilityLoggerMixin(getMetricsCategory(), - FeatureFactory.getFactory(this).getMetricsFeatureProvider())); + getLifecycle().addObserver(new VisibilityLoggerMixin(getMetricsCategory())); } } diff --git a/src/com/android/settings/core/InstrumentedFragment.java b/src/com/android/settings/core/InstrumentedFragment.java index b1215b9ac4a..45db836efcc 100644 --- a/src/com/android/settings/core/InstrumentedFragment.java +++ b/src/com/android/settings/core/InstrumentedFragment.java @@ -18,28 +18,30 @@ import android.content.Context; +import com.android.settings.core.instrumentation.Instrumentable; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.overlay.FeatureFactory; import com.android.settings.survey.SurveyMixin; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.core.lifecycle.ObservableFragment; public abstract class InstrumentedFragment extends ObservableFragment implements Instrumentable { protected MetricsFeatureProvider mMetricsFeatureProvider; - private VisibilityLoggerMixin mVisibilityLoggerMixin; + private final VisibilityLoggerMixin mVisibilityLoggerMixin; - @Override - public void onAttach(Context context) { - mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); - mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), - mMetricsFeatureProvider); + public InstrumentedFragment() { // Mixin that logs visibility change for activity. + mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory()); getLifecycle().addObserver(mVisibilityLoggerMixin); getLifecycle().addObserver(new SurveyMixin(this, getClass().getSimpleName())); + } + + @Override + public void onAttach(Context context) { super.onAttach(context); + mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } @Override diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java index 278676c52ea..7e37115bab0 100644 --- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java +++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java @@ -23,11 +23,11 @@ import android.text.TextUtils; import android.util.Log; +import com.android.settings.core.instrumentation.Instrumentable; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.overlay.FeatureFactory; import com.android.settings.survey.SurveyMixin; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment; /** @@ -44,17 +44,19 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc // metrics placeholder value. Only use this for development. protected final int PLACEHOLDER_METRIC = 10000; - private VisibilityLoggerMixin mVisibilityLoggerMixin; + private final VisibilityLoggerMixin mVisibilityLoggerMixin; - @Override - public void onAttach(Context context) { - mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + public InstrumentedPreferenceFragment() { // Mixin that logs visibility change for activity. - mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), - mMetricsFeatureProvider); + mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory()); getLifecycle().addObserver(mVisibilityLoggerMixin); getLifecycle().addObserver(new SurveyMixin(this, getClass().getSimpleName())); + } + + @Override + public void onAttach(Context context) { super.onAttach(context); + mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } @Override diff --git a/src/com/android/settings/core/instrumentation/EventLogWriter.java b/src/com/android/settings/core/instrumentation/EventLogWriter.java new file mode 100644 index 00000000000..3196f76b323 --- /dev/null +++ b/src/com/android/settings/core/instrumentation/EventLogWriter.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import android.content.Context; +import android.metrics.LogMaker; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; + +/** + * {@link LogWriter} that writes data to eventlog. + */ +public class EventLogWriter implements LogWriter { + + private final MetricsLogger mMetricsLogger = new MetricsLogger(); + + public void visible(Context context, int source, int category) { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_OPEN) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source); + MetricsLogger.action(logMaker); + } + + public void hidden(Context context, int category) { + MetricsLogger.hidden(context, category); + } + + public void action(int category, int value, Pair... taggedData) { + if (taggedData == null || taggedData.length == 0) { + mMetricsLogger.action(category, value); + } else { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION) + .setSubtype(value); + for (Pair pair : taggedData) { + logMaker.addTaggedData(pair.first, pair.second); + } + mMetricsLogger.write(logMaker); + } + } + + public void action(int category, boolean value, Pair... taggedData) { + action(category, value ? 1 : 0, taggedData); + } + + public void action(Context context, int category, Pair... taggedData) { + action(context, category, "", taggedData); + } + + public void actionWithSource(Context context, int source, int category) { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION); + if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) { + logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source); + } + MetricsLogger.action(logMaker); + } + + /** @deprecated use {@link #action(int, int, Pair[])} */ + @Deprecated + public void action(Context context, int category, int value) { + MetricsLogger.action(context, category, value); + } + + /** @deprecated use {@link #action(int, boolean, Pair[])} */ + @Deprecated + public void action(Context context, int category, boolean value) { + MetricsLogger.action(context, category, value); + } + + public void action(Context context, int category, String pkg, + Pair... taggedData) { + if (taggedData == null || taggedData.length == 0) { + MetricsLogger.action(context, category, pkg); + } else { + final LogMaker logMaker = new LogMaker(category) + .setType(MetricsProto.MetricsEvent.TYPE_ACTION) + .setPackageName(pkg); + for (Pair pair : taggedData) { + logMaker.addTaggedData(pair.first, pair.second); + } + MetricsLogger.action(logMaker); + } + } + + public void count(Context context, String name, int value) { + MetricsLogger.count(context, name, value); + } + + public void histogram(Context context, String name, int bucket) { + MetricsLogger.histogram(context, name, bucket); + } +} diff --git a/src/com/android/settings/core/instrumentation/Instrumentable.java b/src/com/android/settings/core/instrumentation/Instrumentable.java new file mode 100644 index 00000000000..f58e140b62a --- /dev/null +++ b/src/com/android/settings/core/instrumentation/Instrumentable.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +public interface Instrumentable { + + int METRICS_CATEGORY_UNKNOWN = 0; + + /** + * Instrumented name for a view as defined in + * {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}. + */ + int getMetricsCategory(); +} diff --git a/src/com/android/settings/core/instrumentation/InstrumentedDialogFragment.java b/src/com/android/settings/core/instrumentation/InstrumentedDialogFragment.java index 0a214f17315..5a9ab56ea99 100644 --- a/src/com/android/settings/core/instrumentation/InstrumentedDialogFragment.java +++ b/src/com/android/settings/core/instrumentation/InstrumentedDialogFragment.java @@ -19,9 +19,6 @@ import com.android.settings.DialogCreatable; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.core.lifecycle.ObservableDialogFragment; public abstract class InstrumentedDialogFragment extends ObservableDialogFragment @@ -41,15 +38,13 @@ public InstrumentedDialogFragment() { public InstrumentedDialogFragment(DialogCreatable dialogCreatable, int dialogId) { mDialogCreatable = dialogCreatable; mDialogId = dialogId; + mLifecycle.addObserver(new VisibilityLoggerMixin(getMetricsCategory())); } + @Override public void onAttach(Context context) { super.onAttach(context); - mMetricsFeatureProvider = FeatureFactory.getFactory(context) - .getMetricsFeatureProvider(); - mLifecycle.addObserver(new VisibilityLoggerMixin(getMetricsCategory(), - mMetricsFeatureProvider)); - mLifecycle.onAttach(context); + mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); } } diff --git a/src/com/android/settings/core/instrumentation/LogWriter.java b/src/com/android/settings/core/instrumentation/LogWriter.java new file mode 100644 index 00000000000..062d46f759f --- /dev/null +++ b/src/com/android/settings/core/instrumentation/LogWriter.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import android.content.Context; +import android.util.Pair; + +/** + * Generic log writer interface. + */ +public interface LogWriter { + + /** + * Logs a visibility event when view becomes visible. + */ + void visible(Context context, int source, int category); + + /** + * Logs a visibility event when view becomes hidden. + */ + void hidden(Context context, int category); + + /** + * Logs a user action. + */ + void action(int category, int value, Pair... taggedData); + + /** + * Logs a user action. + */ + void action(int category, boolean value, Pair... taggedData); + + /** + * Logs an user action. + */ + void action(Context context, int category, Pair... taggedData); + + /** + * Logs an user action. + */ + void actionWithSource(Context context, int source, int category); + + /** + * Logs an user action. + * @deprecated use {@link #action(int, int, Pair[])} + */ + @Deprecated + void action(Context context, int category, int value); + + /** + * Logs an user action. + * @deprecated use {@link #action(int, boolean, Pair[])} + */ + @Deprecated + void action(Context context, int category, boolean value); + + /** + * Logs an user action. + */ + void action(Context context, int category, String pkg, Pair... taggedData); + + /** + * Logs a count. + */ + void count(Context context, String name, int value); + + /** + * Logs a histogram event. + */ + void histogram(Context context, String name, int bucket); +} diff --git a/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java b/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java new file mode 100644 index 00000000000..166cbb84471 --- /dev/null +++ b/src/com/android/settings/core/instrumentation/MetricsFeatureProvider.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.util.Pair; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * FeatureProvider for metrics. + */ +public class MetricsFeatureProvider { + private List mLoggerWriters; + + public MetricsFeatureProvider() { + mLoggerWriters = new ArrayList<>(); + installLogWriters(); + } + + protected void installLogWriters() { + mLoggerWriters.add(new EventLogWriter()); + } + + public void visible(Context context, int source, int category) { + for (LogWriter writer : mLoggerWriters) { + writer.visible(context, source, category); + } + } + + public void hidden(Context context, int category) { + for (LogWriter writer : mLoggerWriters) { + writer.hidden(context, category); + } + } + + public void actionWithSource(Context context, int source, int category) { + for (LogWriter writer : mLoggerWriters) { + writer.actionWithSource(context, source, category); + } + } + + /** + * Logs a user action. Includes the elapsed time since the containing + * fragment has been visible. + */ + public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(category, value, + sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); + } + } + + /** + * Logs a user action. Includes the elapsed time since the containing + * fragment has been visible. + */ + public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(category, value, + sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); + } + } + + public void action(Context context, int category, Pair... taggedData) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, taggedData); + } + } + + /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */ + @Deprecated + public void action(Context context, int category, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, value); + } + } + + /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */ + @Deprecated + public void action(Context context, int category, boolean value) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, value); + } + } + + public void action(Context context, int category, String pkg, + Pair... taggedData) { + for (LogWriter writer : mLoggerWriters) { + writer.action(context, category, pkg, taggedData); + } + } + + public void count(Context context, String name, int value) { + for (LogWriter writer : mLoggerWriters) { + writer.count(context, name, value); + } + } + + public void histogram(Context context, String name, int bucket) { + for (LogWriter writer : mLoggerWriters) { + writer.histogram(context, name, bucket); + } + } + + public int getMetricsCategory(Object object) { + if (object == null || !(object instanceof Instrumentable)) { + return MetricsEvent.VIEW_UNKNOWN; + } + return ((Instrumentable) object).getMetricsCategory(); + } + + public void logDashboardStartIntent(Context context, Intent intent, + int sourceMetricsCategory) { + if (intent == null) { + return; + } + final ComponentName cn = intent.getComponent(); + if (cn == null) { + final String action = intent.getAction(); + if (TextUtils.isEmpty(action)) { + // Not loggable + return; + } + action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action, + Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + return; + } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) { + // Going to a Setting internal page, skip click logging in favor of page's own + // visibility logging. + return; + } + action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(), + Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); + } + + private Pair sinceVisibleTaggedData(long timestamp) { + return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp); + } +} diff --git a/src/com/android/settings/core/instrumentation/SharedPreferencesLogger.java b/src/com/android/settings/core/instrumentation/SharedPreferencesLogger.java new file mode 100644 index 00000000000..dee40c043ce --- /dev/null +++ b/src/com/android/settings/core/instrumentation/SharedPreferencesLogger.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.overlay.FeatureFactory; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; + +public class SharedPreferencesLogger implements SharedPreferences { + + private static final String LOG_TAG = "SharedPreferencesLogger"; + + private final String mTag; + private final Context mContext; + private final MetricsFeatureProvider mMetricsFeature; + private final Set mPreferenceKeySet; + + public SharedPreferencesLogger(Context context, String tag) { + mContext = context; + mTag = tag; + mMetricsFeature = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + mPreferenceKeySet = new ConcurrentSkipListSet<>(); + } + + @Override + public Map getAll() { + return null; + } + + @Override + public String getString(String key, @Nullable String defValue) { + return defValue; + } + + @Override + public Set getStringSet(String key, @Nullable Set defValues) { + return defValues; + } + + @Override + public int getInt(String key, int defValue) { + return defValue; + } + + @Override + public long getLong(String key, long defValue) { + return defValue; + } + + @Override + public float getFloat(String key, float defValue) { + return defValue; + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + return defValue; + } + + @Override + public boolean contains(String key) { + return false; + } + + @Override + public Editor edit() { + return new EditorLogger(); + } + + @Override + public void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + } + + @Override + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + } + + private void logValue(String key, Object value) { + logValue(key, value, false /* forceLog */); + } + + private void logValue(String key, Object value, boolean forceLog) { + final String prefKey = buildPrefKey(mTag, key); + if (!forceLog && !mPreferenceKeySet.contains(prefKey)) { + // Pref key doesn't exist in set, this is initial display so we skip metrics but + // keeps track of this key. + mPreferenceKeySet.add(prefKey); + return; + } + // TODO: Remove count logging to save some resource. + mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1); + + final Pair valueData; + if (value instanceof Long) { + final Long longVal = (Long) value; + final int intVal; + if (longVal > Integer.MAX_VALUE) { + intVal = Integer.MAX_VALUE; + } else if (longVal < Integer.MIN_VALUE) { + intVal = Integer.MIN_VALUE; + } else { + intVal = longVal.intValue(); + } + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, + intVal); + } else if (value instanceof Integer) { + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, + value); + } else if (value instanceof Boolean) { + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, + (Boolean) value ? 1 : 0); + } else if (value instanceof Float) { + valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, + value); + } else if (value instanceof String) { + Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value); + valueData = null; + } else { + Log.w(LOG_TAG, "Tried to log unloggable object" + value); + valueData = null; + } + if (valueData != null) { + // Pref key exists in set, log it's change in metrics. + mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, + Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey), + valueData); + } + } + + @VisibleForTesting + void logPackageName(String key, String value) { + final String prefKey = mTag + "/" + key; + mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value, + Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey)); + } + + private void safeLogValue(String key, String value) { + new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value); + } + + public static String buildCountName(String prefKey, Object value) { + return prefKey + "|" + value; + } + + public static String buildPrefKey(String tag, String key) { + return tag + "/" + key; + } + + private class AsyncPackageCheck extends AsyncTask { + @Override + protected Void doInBackground(String... params) { + String key = params[0]; + String value = params[1]; + PackageManager pm = mContext.getPackageManager(); + try { + // Check if this might be a component. + ComponentName name = ComponentName.unflattenFromString(value); + if (value != null) { + value = name.getPackageName(); + } + } catch (Exception e) { + } + try { + pm.getPackageInfo(value, PackageManager.MATCH_ANY_USER); + logPackageName(key, value); + } catch (PackageManager.NameNotFoundException e) { + // Clearly not a package, and it's unlikely this preference is in prefSet, so + // lets force log it. + logValue(key, value, true /* forceLog */); + } + return null; + } + } + + public class EditorLogger implements Editor { + @Override + public Editor putString(String key, @Nullable String value) { + safeLogValue(key, value); + return this; + } + + @Override + public Editor putStringSet(String key, @Nullable Set values) { + safeLogValue(key, TextUtils.join(",", values)); + return this; + } + + @Override + public Editor putInt(String key, int value) { + logValue(key, value); + return this; + } + + @Override + public Editor putLong(String key, long value) { + logValue(key, value); + return this; + } + + @Override + public Editor putFloat(String key, float value) { + logValue(key, value); + return this; + } + + @Override + public Editor putBoolean(String key, boolean value) { + logValue(key, value); + return this; + } + + @Override + public Editor remove(String key) { + return this; + } + + @Override + public Editor clear() { + return this; + } + + @Override + public boolean commit() { + return true; + } + + @Override + public void apply() { + } + } +} diff --git a/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java b/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java new file mode 100644 index 00000000000..2fe2a3beb1d --- /dev/null +++ b/src/com/android/settings/core/instrumentation/VisibilityLoggerMixin.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import android.os.SystemClock; +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.SettingsActivity; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnAttach; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; + +import static com.android.settings.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; + +/** + * Logs visibility change of a fragment. + */ +public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause, OnAttach { + + private static final String TAG = "VisibilityLoggerMixin"; + + private final int mMetricsCategory; + + private MetricsFeatureProvider mMetricsFeature; + private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN; + private long mVisibleTimestamp; + + public VisibilityLoggerMixin(int metricsCategory) { + // MetricsFeature will be set during onAttach. + this(metricsCategory, null /* metricsFeature */); + } + + public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) { + mMetricsCategory = metricsCategory; + mMetricsFeature = metricsFeature; + } + + @Override + public void onAttach(Context context) { + mMetricsFeature = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + } + + @Override + public void onResume() { + mVisibleTimestamp = SystemClock.elapsedRealtime(); + if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { + mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory); + } + } + + @Override + public void onPause() { + mVisibleTimestamp = 0; + if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { + mMetricsFeature.hidden(null /* context */, mMetricsCategory); + } + } + + /** + * Sets source metrics category for this logger. Source is the caller that opened this UI. + */ + public void setSourceMetricsCategory(Activity activity) { + if (mSourceMetricsCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN || activity == null) { + return; + } + final Intent intent = activity.getIntent(); + if (intent == null) { + return; + } + mSourceMetricsCategory = intent.getIntExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, + MetricsProto.MetricsEvent.VIEW_UNKNOWN); + } + + /** Returns elapsed time since onResume() */ + public long elapsedTimeSinceVisible() { + if (mVisibleTimestamp == 0) { + return 0; + } + return SystemClock.elapsedRealtime() - mVisibleTimestamp; + } +} diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index 2d35ea78d33..97eef1329d5 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -41,6 +41,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.R.id; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardData.SuggestionConditionHeaderData; import com.android.settings.dashboard.conditional.Condition; import com.android.settings.dashboard.conditional.ConditionAdapter; @@ -49,7 +50,6 @@ import com.android.settings.dashboard.suggestions.SuggestionDismissController; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.Utils; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; diff --git a/src/com/android/settings/dashboard/DashboardAdapterV2.java b/src/com/android/settings/dashboard/DashboardAdapterV2.java index ad93e4c6065..a422ae43328 100644 --- a/src/com/android/settings/dashboard/DashboardAdapterV2.java +++ b/src/com/android/settings/dashboard/DashboardAdapterV2.java @@ -39,13 +39,13 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.R.id; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardDataV2.ConditionHeaderData; import com.android.settings.dashboard.conditional.Condition; import com.android.settings.dashboard.conditional.ConditionAdapterV2; import com.android.settings.dashboard.suggestions.SuggestionAdapterV2; import com.android.settings.dashboard.suggestions.SuggestionControllerMixin; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index a14d9e9a76d..a06fee9fbc1 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -41,9 +41,8 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.core.FeatureFlags; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.drawer.CategoryManager; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.ProfileSelectDialog; @@ -160,8 +159,7 @@ public void bindPreferenceToTile(Activity activity, int sourceMetricsCategory, P pref.setFragment(clsName); } else if (tile.intent != null) { final Intent intent = new Intent(tile.intent); - intent.putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, - sourceMetricsCategory); + intent.putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, sourceMetricsCategory); if (action != null) { intent.setAction(action); } @@ -210,7 +208,7 @@ public void openTileIntent(Activity activity, Tile tile) { return; } final Intent intent = new Intent(tile.intent) - .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, + .putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, MetricsEvent.DASHBOARD_SUMMARY) .putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); diff --git a/src/com/android/settings/dashboard/conditional/Condition.java b/src/com/android/settings/dashboard/conditional/Condition.java index d66440e9e89..05783bde798 100644 --- a/src/com/android/settings/dashboard/conditional/Condition.java +++ b/src/com/android/settings/dashboard/conditional/Condition.java @@ -24,8 +24,8 @@ import android.support.annotation.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public abstract class Condition { diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapter.java b/src/com/android/settings/dashboard/conditional/ConditionAdapter.java index d84aa7c9eac..eb768e50d0e 100644 --- a/src/com/android/settings/dashboard/conditional/ConditionAdapter.java +++ b/src/com/android/settings/dashboard/conditional/ConditionAdapter.java @@ -27,13 +27,13 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardAdapter; import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder; import com.android.settings.dashboard.DashboardData; import com.android.settings.dashboard.DashboardData.HeaderMode; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.WirelessUtils; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.List; import java.util.Objects; diff --git a/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java b/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java index 8db57f79678..3f3e5c91daf 100644 --- a/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java +++ b/src/com/android/settings/dashboard/conditional/ConditionAdapterV2.java @@ -27,10 +27,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.WirelessUtils; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.List; import java.util.Objects; diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java index 2b79a9babfe..fc1102901c0 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapter.java @@ -27,10 +27,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardAdapter.DashboardItemHolder; import com.android.settings.dashboard.DashboardAdapter.IconCache; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.List; import java.util.Objects; diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java b/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java index e04ae936418..afd0e08b64d 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionAdapterV2.java @@ -31,10 +31,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardAdapterV2.DashboardItemHolder; import com.android.settings.dashboard.DashboardAdapterV2.IconCache; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java index 8cd1675ac2b..fe19b958c2d 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionFeatureProviderImpl.java @@ -36,6 +36,7 @@ import com.android.settings.Settings.DoubleTwistSuggestionActivity; import com.android.settings.Settings.NightDisplaySuggestionActivity; import com.android.settings.Settings.SwipeToNotificationSuggestionActivity; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fingerprint.FingerprintEnrollSuggestionActivity; import com.android.settings.fingerprint.FingerprintSuggestionActivity; import com.android.settings.gestures.DoubleTapPowerPreferenceController; @@ -48,7 +49,6 @@ import com.android.settings.support.NewDeviceIntroSuggestionActivity; import com.android.settings.wallpaper.WallpaperSuggestionActivity; import com.android.settings.wifi.WifiCallingSuggestionActivity; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.drawer.Tile; import java.util.List; diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java index b59da9d7ff8..ff568c7c117 100644 --- a/src/com/android/settings/datausage/DataSaverBackend.java +++ b/src/com/android/settings/datausage/DataSaverBackend.java @@ -25,8 +25,8 @@ import android.util.SparseIntArray; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; diff --git a/src/com/android/settings/datetime/ZonePicker.java b/src/com/android/settings/datetime/ZonePicker.java index dc691275e31..57c340c6809 100644 --- a/src/com/android/settings/datetime/ZonePicker.java +++ b/src/com/android/settings/datetime/ZonePicker.java @@ -35,9 +35,8 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; -import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; +import com.android.settings.core.instrumentation.Instrumentable; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.datetime.ZoneGetter; import java.text.Collator; @@ -58,7 +57,8 @@ public class ZonePicker extends ListFragment implements Instrumentable { private static final int MENU_TIMEZONE = Menu.FIRST+1; private static final int MENU_ALPHABETICAL = Menu.FIRST; - private VisibilityLoggerMixin mVisibilityLoggerMixin; + private final VisibilityLoggerMixin mVisibilityLoggerMixin = + new VisibilityLoggerMixin(getMetricsCategory()); private boolean mSortedByTimezone; @@ -144,6 +144,12 @@ public static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) { return -1; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + mVisibilityLoggerMixin.onAttach(context); + } + @Override public int getMetricsCategory() { return MetricsProto.MetricsEvent.ZONE_PICKER; @@ -163,13 +169,6 @@ public void onActivityCreated(Bundle savedInstanceState) { activity.setTitle(R.string.date_time_set_timezone); } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), - FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()); - } - @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarController.java b/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarController.java index a20afa1060a..8ab1a07fb13 100644 --- a/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarController.java +++ b/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarController.java @@ -23,10 +23,10 @@ import android.support.v7.preference.Preference; import android.widget.Switch; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.Preconditions; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.SwitchBar; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; /** Handles the logic for flipping the storage management toggle on a {@link SwitchBar}. */ public class AutomaticStorageManagerSwitchBarController diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java index 0f3bfb8ec76..2641f5d941f 100644 --- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java @@ -34,11 +34,11 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnResume; diff --git a/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java b/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java index 8ab21b36fdb..717d7650b75 100644 --- a/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java @@ -25,12 +25,12 @@ import android.support.v7.preference.PreferenceScreen; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.deletionhelper.ActivationWarningFragment; import com.android.settings.widget.MasterSwitchController; import com.android.settings.widget.MasterSwitchPreference; import com.android.settings.widget.SwitchWidgetController; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnResume; diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index 36232985934..1149b99bce6 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -38,11 +38,11 @@ import com.android.settings.Utils; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.deviceinfo.PrivateVolumeSettings.SystemInfoFragment; import com.android.settings.deviceinfo.StorageItemPreference; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.deviceinfo.StorageMeasurement; import com.android.settingslib.deviceinfo.StorageVolumeProvider; diff --git a/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceController.java b/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceController.java index f3d17d58939..68a21cebf9c 100644 --- a/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceController.java +++ b/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceController.java @@ -27,11 +27,11 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.search.DatabaseIndexingUtils; import com.android.settings.search.InlineSwitchPayload; import com.android.settings.search.ResultPayload; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class AmbientDisplayNotificationsPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, diff --git a/src/com/android/settings/display/AmbientDisplaySettings.java b/src/com/android/settings/display/AmbientDisplaySettings.java index 187325c0c7f..24aede05ef6 100644 --- a/src/com/android/settings/display/AmbientDisplaySettings.java +++ b/src/com/android/settings/display/AmbientDisplaySettings.java @@ -23,13 +23,13 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.gestures.DoubleTapScreenPreferenceController; import com.android.settings.gestures.PickupGesturePreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.ArrayList; diff --git a/src/com/android/settings/display/AutoRotatePreferenceController.java b/src/com/android/settings/display/AutoRotatePreferenceController.java index 2134b882638..c7f6af175a1 100644 --- a/src/com/android/settings/display/AutoRotatePreferenceController.java +++ b/src/com/android/settings/display/AutoRotatePreferenceController.java @@ -20,9 +20,9 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.view.RotationPolicy; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; diff --git a/src/com/android/settings/display/ThemePreferenceController.java b/src/com/android/settings/display/ThemePreferenceController.java index 9c1314ebc5e..d1341dd7435 100644 --- a/src/com/android/settings/display/ThemePreferenceController.java +++ b/src/com/android/settings/display/ThemePreferenceController.java @@ -29,9 +29,9 @@ import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import libcore.util.Objects; diff --git a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java index a52433b374c..c0347466812 100644 --- a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java +++ b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java @@ -48,6 +48,7 @@ import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.ActionButtonPreference; import com.android.settings.wrapper.DevicePolicyManagerWrapper; @@ -55,7 +56,6 @@ import com.android.settingslib.applications.AppUtils; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnDestroy; diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 91f35e2533a..5d95dd2ec71 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -49,10 +49,10 @@ import com.android.settings.core.FeatureFlags; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.Utils; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnDestroy; diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index e0954e57f25..ec54291e3ed 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -47,6 +47,7 @@ import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.applications.manageapplications.ManageApplications; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.display.AmbientDisplayPreferenceController; import com.android.settings.display.AutoBrightnessPreferenceController; @@ -60,7 +61,6 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.ArrayList; diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java b/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java index 605591dda15..c50d5808629 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummaryLegacy.java @@ -54,6 +54,7 @@ import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.applications.manageapplications.ManageApplications; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.display.AmbientDisplayPreferenceController; import com.android.settings.display.AutoBrightnessPreferenceController; @@ -66,7 +67,6 @@ import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController; import com.android.settings.fuelgauge.anomaly.AnomalyUtils; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.AbstractPreferenceController; import java.util.ArrayList; diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java index 3dde95eff5b..39d51dc08af 100644 --- a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java +++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java @@ -24,6 +24,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fuelgauge.anomaly.action.AnomalyAction; import com.android.settings.fuelgauge.anomaly.action.ForceStopAction; import com.android.settings.fuelgauge.anomaly.action.LocationCheckAction; @@ -32,7 +33,6 @@ import com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector; import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector; import com.android.settings.fuelgauge.anomaly.checker.WakeupAlarmAnomalyDetector; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java index d7de5a7ac2a..3ee89d1c4a2 100644 --- a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java +++ b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java @@ -20,9 +20,9 @@ import android.util.Pair; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; /** * Abstract class for anomaly action, which is triggered if we need to handle the anomaly diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/BatteryTipAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryTipAction.java index 1bf08b7b64d..9fa69fd02c0 100644 --- a/src/com/android/settings/fuelgauge/batterytip/actions/BatteryTipAction.java +++ b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryTipAction.java @@ -18,7 +18,7 @@ import android.content.Context; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; /** * Abstract class for battery tip action, which is triggered if we need to handle the battery tip diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/SmartBatteryAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/SmartBatteryAction.java index cbd15812e5a..a19471e6d7b 100644 --- a/src/com/android/settings/fuelgauge/batterytip/actions/SmartBatteryAction.java +++ b/src/com/android/settings/fuelgauge/batterytip/actions/SmartBatteryAction.java @@ -22,8 +22,8 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fuelgauge.SmartBatterySettings; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class SmartBatteryAction extends BatteryTipAction { private SettingsActivity mSettingsActivity; diff --git a/src/com/android/settings/inputmethod/UserDictionarySettings.java b/src/com/android/settings/inputmethod/UserDictionarySettings.java index 3bbc581ac82..9680af10c60 100644 --- a/src/com/android/settings/inputmethod/UserDictionarySettings.java +++ b/src/com/android/settings/inputmethod/UserDictionarySettings.java @@ -42,11 +42,10 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto; -import com.android.settings.overlay.FeatureFactory; import com.android.settings.R; import com.android.settings.SettingsActivity; -import com.android.settingslib.core.instrumentation.Instrumentable; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; +import com.android.settings.core.instrumentation.Instrumentable; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; public class UserDictionarySettings extends ListFragment implements Instrumentable, LoaderManager.LoaderCallbacks { @@ -60,7 +59,8 @@ public class UserDictionarySettings extends ListFragment implements Instrumentab private static final int OPTIONS_MENU_ADD = Menu.FIRST; private static final int LOADER_ID = 1; - private VisibilityLoggerMixin mVisibilityLoggerMixin; + private final VisibilityLoggerMixin mVisibilityLoggerMixin = + new VisibilityLoggerMixin(getMetricsCategory()); private Cursor mCursor; private String mLocale; @@ -70,13 +70,16 @@ public int getMetricsCategory() { return MetricsProto.MetricsEvent.USER_DICTIONARY_SETTINGS; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + mVisibilityLoggerMixin.onAttach(context); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), - FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()); - final Intent intent = getActivity().getIntent(); final String localeFromIntent = null == intent ? null : intent.getStringExtra("locale"); diff --git a/src/com/android/settings/network/AirplaneModePreferenceController.java b/src/com/android/settings/network/AirplaneModePreferenceController.java index 0b771791be7..17cf211e9dd 100644 --- a/src/com/android/settings/network/AirplaneModePreferenceController.java +++ b/src/com/android/settings/network/AirplaneModePreferenceController.java @@ -28,10 +28,10 @@ import com.android.internal.telephony.TelephonyProperties; import com.android.settings.AirplaneModeEnabler; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settings.R; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index 74c191006d4..4b1da310bdd 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -31,13 +31,13 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.network.MobilePlanPreferenceController.MobilePlanPreferenceHost; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.wifi.WifiMasterSwitchPreferenceController; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.ArrayList; diff --git a/src/com/android/settings/notification/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/AbstractZenModePreferenceController.java index 9180791fd55..81ceca19c77 100644 --- a/src/com/android/settings/notification/AbstractZenModePreferenceController.java +++ b/src/com/android/settings/notification/AbstractZenModePreferenceController.java @@ -34,9 +34,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; diff --git a/src/com/android/settings/notification/ZenRulePreference.java b/src/com/android/settings/notification/ZenRulePreference.java index fee390f5edf..71938732a2b 100644 --- a/src/com/android/settings/notification/ZenRulePreference.java +++ b/src/com/android/settings/notification/ZenRulePreference.java @@ -30,10 +30,10 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.utils.ManagedServiceSettings; import com.android.settings.utils.ZenServiceListing; import com.android.settingslib.TwoTargetPreference; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.Map; @@ -145,4 +145,4 @@ private String computeRuleSummary(AutomaticZenRule rule, boolean isSystemRule, ? mContext.getResources().getString(R.string.switch_off_text) : mContext.getResources().getString(R.string.switch_on_text); } -} +} \ No newline at end of file diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 08057664c1c..dc9df8399f9 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -24,6 +24,7 @@ import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.bluetooth.BluetoothFeatureProvider; import com.android.settings.connecteddevice.SmsMirroringFeatureProvider; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.datausage.DataPlanFeatureProvider; @@ -35,7 +36,6 @@ import com.android.settings.search.SearchFeatureProvider; import com.android.settings.slices.SlicesFeatureProvider; import com.android.settings.users.UserFeatureProvider; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; /** * Abstract class for creating feature controllers. Allows OEM implementations to define their own diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index f817d4bb218..275ebb66865 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -29,6 +29,7 @@ import com.android.settings.bluetooth.BluetoothFeatureProviderImpl; import com.android.settings.connecteddevice.SmsMirroringFeatureProvider; import com.android.settings.connecteddevice.SmsMirroringFeatureProviderImpl; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProviderImpl; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; @@ -54,7 +55,6 @@ import com.android.settings.wrapper.ConnectivityManagerWrapper; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settings.wrapper.IPackageManagerWrapper; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.wrapper.PackageManagerWrapper; /** diff --git a/src/com/android/settings/widget/SwitchBar.java b/src/com/android/settings/widget/SwitchBar.java index 3be5eca3c2e..749ec0a919b 100644 --- a/src/com/android/settings/widget/SwitchBar.java +++ b/src/com/android/settings/widget/SwitchBar.java @@ -39,9 +39,9 @@ import android.widget.TextView; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/settings/wifi/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java index c5e79b2db49..9c431422371 100644 --- a/src/com/android/settings/wifi/WifiEnabler.java +++ b/src/com/android/settings/wifi/WifiEnabler.java @@ -33,9 +33,9 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.SwitchWidgetController; import com.android.settings.wrapper.ConnectivityManagerWrapper; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.WirelessUtils; diff --git a/src/com/android/settings/wifi/WifiMasterSwitchPreferenceController.java b/src/com/android/settings/wifi/WifiMasterSwitchPreferenceController.java index 8843d93775a..de1b030f3dc 100644 --- a/src/com/android/settings/wifi/WifiMasterSwitchPreferenceController.java +++ b/src/com/android/settings/wifi/WifiMasterSwitchPreferenceController.java @@ -19,12 +19,12 @@ import android.support.v7.preference.PreferenceScreen; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.R; import com.android.settings.widget.SummaryUpdater; import com.android.settings.widget.MasterSwitchPreference; import com.android.settings.widget.MasterSwitchController; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java index 13ffd5bee44..70ee20de03d 100644 --- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java @@ -54,6 +54,7 @@ import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.ActionButtonPreference; import com.android.settings.widget.EntityHeaderController; import com.android.settings.wifi.WifiDetailPreference; @@ -62,7 +63,6 @@ import com.android.settings.wifi.WifiUtils; import com.android.settings.wrapper.ConnectivityManagerWrapper; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; diff --git a/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java b/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java index 3a7d094747f..942634a0a71 100644 --- a/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/SettingsDialogFragmentTest.java @@ -17,7 +17,6 @@ import android.app.Dialog; import android.app.Fragment; -import android.content.Context; import org.junit.Before; import org.junit.Test; @@ -39,8 +38,6 @@ public class SettingsDialogFragmentTest { private static final int DIALOG_ID = 15; - @Mock - private Context mContext; @Mock private DialogCreatableFragment mDialogCreatable; private SettingsPreferenceFragment.SettingsDialogFragment mDialogFragment; @@ -56,10 +53,9 @@ public void testGetMetrics_shouldGetMetricFromDialogCreatable() { mDialogFragment = new SettingsPreferenceFragment.SettingsDialogFragment(mDialogCreatable, DIALOG_ID); - mDialogFragment.onAttach(mContext); mDialogFragment.getMetricsCategory(); - // getDialogMetricsCategory called in onAttach, and explicitly in test. + // getDialogMetricsCategory called in constructor, and explicitly in test. verify(mDialogCreatable, times(2)).getDialogMetricsCategory(DIALOG_ID); } @@ -70,7 +66,6 @@ public void testGetInvalidMetricsValue_shouldCrash() { try { mDialogFragment = new SettingsPreferenceFragment.SettingsDialogFragment( mDialogCreatable, DIALOG_ID); - mDialogFragment.onAttach(mContext); } catch (IllegalStateException e) { // getDialogMetricsCategory called in constructor verify(mDialogCreatable).getDialogMetricsCategory(DIALOG_ID); diff --git a/tests/robotests/src/com/android/settings/applications/instantapps/InstantAppButtonsControllerTest.java b/tests/robotests/src/com/android/settings/applications/instantapps/InstantAppButtonsControllerTest.java index f85d43aac6a..5c0badcc475 100644 --- a/tests/robotests/src/com/android/settings/applications/instantapps/InstantAppButtonsControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/instantapps/InstantAppButtonsControllerTest.java @@ -42,9 +42,9 @@ import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.wrapper.PackageManagerWrapper; import org.junit.Before; diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java index 71020be315a..e9d37f6a0c2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java @@ -32,11 +32,11 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java index 828b5a17335..b973edb674d 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothEnablerTest.java @@ -29,13 +29,13 @@ import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.widget.MasterSwitchController; import com.android.settings.widget.MasterSwitchPreference; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.BeforeClass; diff --git a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java index c3b22b3a98c..8666ce3c13c 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java @@ -26,11 +26,11 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java b/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java index 867b5df7336..4455549db12 100644 --- a/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java +++ b/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentableFragmentCodeInspector.java @@ -20,7 +20,6 @@ import android.util.ArraySet; import com.android.settings.core.codeinspection.CodeInspector; -import com.android.settingslib.core.instrumentation.Instrumentable; import java.util.ArrayList; import java.util.List; diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentedDialogFragmentTest.java b/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentedDialogFragmentTest.java index 8ad2d696846..9e37896fa62 100644 --- a/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentedDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/core/instrumentation/InstrumentedDialogFragmentTest.java @@ -21,7 +21,6 @@ import com.android.settings.TestConfig; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java b/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java index 2950c072ad6..da48f15c4c4 100644 --- a/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java +++ b/tests/robotests/src/com/android/settings/core/instrumentation/MetricsFeatureProviderTest.java @@ -31,9 +31,6 @@ import com.android.settings.TestConfig; import com.android.settings.overlay.FeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settingslib.core.instrumentation.LogWriter; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import org.junit.Before; import org.junit.Test; @@ -61,6 +58,7 @@ public class MetricsFeatureProviderTest { @Mock private VisibilityLoggerMixin mockVisibilityLogger; private Context mContext; + private MetricsFeatureProvider mProvider; @Captor private ArgumentCaptor mPairCaptor; @@ -69,6 +67,12 @@ public class MetricsFeatureProviderTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mProvider = new MetricsFeatureProvider(); + List writers = new ArrayList<>(); + writers.add(mockLogWriter); + ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers); + + when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME); } @Test @@ -80,4 +84,60 @@ public void getFactory_shouldReuseCachedInstance() { assertThat(feature1 == feature2).isTrue(); } + + @Test + public void logDashboardStartIntent_intentEmpty_shouldNotLog() { + mProvider.logDashboardStartIntent(mContext, null /* intent */, + MetricsEvent.SETTINGS_GESTURES); + + verifyNoMoreInteractions(mockLogWriter); + } + + @Test + public void logDashboardStartIntent_intentHasNoComponent_shouldLog() { + final Intent intent = new Intent(Intent.ACTION_ASSIST); + + mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); + + verify(mockLogWriter).action( + eq(mContext), + eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), + anyString(), + eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); + } + + @Test + public void logDashboardStartIntent_intentIsExternal_shouldLog() { + final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls")); + + mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); + + verify(mockLogWriter).action( + eq(mContext), + eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), + anyString(), + eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); + } + + @Test + public void action_BooleanLogsElapsedTime() { + mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN); + verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture()); + + Pair value = mPairCaptor.getValue(); + assertThat(value.first instanceof Integer).isTrue(); + assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); + assertThat(value.second).isEqualTo(ELAPSED_TIME); + } + + @Test + public void action_IntegerLogsElapsedTime() { + mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER); + verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture()); + + Pair value = mPairCaptor.getValue(); + assertThat(value.first instanceof Integer).isTrue(); + assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); + assertThat(value.second).isEqualTo(ELAPSED_TIME); + } } diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/SharedPreferenceLoggerTest.java b/tests/robotests/src/com/android/settings/core/instrumentation/SharedPreferenceLoggerTest.java new file mode 100644 index 00000000000..c80e3a89d8d --- /dev/null +++ b/tests/robotests/src/com/android/settings/core/instrumentation/SharedPreferenceLoggerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Pair; + +import com.android.settings.TestConfig; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import com.google.common.truth.Platform; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SharedPreferenceLoggerTest { + + private static final String TEST_TAG = "tag"; + private static final String TEST_KEY = "key"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + + private ArgumentMatcher> mNamePairMatcher; + private FakeFeatureFactory mFactory; + private MetricsFeatureProvider mMetricsFeature; + private SharedPreferencesLogger mSharedPrefLogger; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + mFactory = FakeFeatureFactory.setupForTest(); + mMetricsFeature = mFactory.metricsFeatureProvider; + + mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG); + mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class); + } + + @Test + public void putInt_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putInt(TEST_KEY, 1); + editor.putInt(TEST_KEY, 1); + editor.putInt(TEST_KEY, 1); + editor.putInt(TEST_KEY, 2); + editor.putInt(TEST_KEY, 2); + editor.putInt(TEST_KEY, 2); + editor.putInt(TEST_KEY, 2); + + verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); + } + + @Test + public void putBoolean_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putBoolean(TEST_KEY, true); + editor.putBoolean(TEST_KEY, true); + editor.putBoolean(TEST_KEY, false); + editor.putBoolean(TEST_KEY, false); + editor.putBoolean(TEST_KEY, false); + + + verify(mMetricsFeature).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true))); + verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false))); + } + + @Test + public void putLong_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, 2); + + verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); + } + + @Test + public void putLong_biggerThanIntMax_shouldLogIntMax() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + final long veryBigNumber = 500L + Integer.MAX_VALUE; + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, veryBigNumber); + + verify(mMetricsFeature).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches( + FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE))); + } + + @Test + public void putLong_smallerThanIntMin_shouldLogIntMin() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + final long veryNegativeNumber = -500L + Integer.MIN_VALUE; + editor.putLong(TEST_KEY, 1); + editor.putLong(TEST_KEY, veryNegativeNumber); + + verify(mMetricsFeature).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches( + FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE))); + } + + @Test + public void putFloat_shouldNotLogInitialPut() { + final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 1); + editor.putFloat(TEST_KEY, 2); + + verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), + argThat(mNamePairMatcher), + argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class))); + } + + @Test + public void logPackage_shouldUseLogPackageApi() { + mSharedPrefLogger.logPackageName("key", "com.android.settings"); + verify(mMetricsFeature).action(any(Context.class), + eq(ACTION_SETTINGS_PREFERENCE_CHANGE), + eq("com.android.settings"), + any(Pair.class)); + } + + private ArgumentMatcher> pairMatches(int tag, Class clazz) { + return pair -> pair.first == tag && Platform.isInstanceOfType(pair.second, clazz); + } + + private ArgumentMatcher> pairMatches(int tag, boolean bool) { + return pair -> pair.first == tag + && Platform.isInstanceOfType(pair.second, Integer.class) + && pair.second.equals((bool ? 1 : 0)); + } + + private ArgumentMatcher> pairMatches(int tag, int val) { + return pair -> pair.first == tag + && Platform.isInstanceOfType(pair.second, Integer.class) + && pair.second.equals(val); + } +} diff --git a/tests/robotests/src/com/android/settings/core/instrumentation/VisibilityLoggerMixinTest.java b/tests/robotests/src/com/android/settings/core/instrumentation/VisibilityLoggerMixinTest.java new file mode 100644 index 00000000000..1a47a66f25e --- /dev/null +++ b/tests/robotests/src/com/android/settings/core/instrumentation/VisibilityLoggerMixinTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 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.core.instrumentation; + +import static com.android.settings.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; + +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.SettingsActivity; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class VisibilityLoggerMixinTest { + + @Mock + private MetricsFeatureProvider mMetricsFeature; + + private VisibilityLoggerMixin mMixin; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, mMetricsFeature); + } + + @Test + public void shouldLogVisibleOnResume() { + mMixin.onResume(); + + verify(mMetricsFeature, times(1)) + .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.VIEW_UNKNOWN), + eq(TestInstrumentable.TEST_METRIC)); + } + + @Test + public void shouldLogVisibleWithSource() { + final Intent sourceIntent = new Intent() + .putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, + MetricsProto.MetricsEvent.SETTINGS_GESTURES); + final Activity activity = mock(Activity.class); + when(activity.getIntent()).thenReturn(sourceIntent); + mMixin.setSourceMetricsCategory(activity); + mMixin.onResume(); + + verify(mMetricsFeature, times(1)) + .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES), + eq(TestInstrumentable.TEST_METRIC)); + } + + @Test + public void shouldLogHideOnPause() { + mMixin.onPause(); + + verify(mMetricsFeature, times(1)) + .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC)); + } + + @Test + public void shouldNotLogIfMetricsFeatureIsNull() { + mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC); + mMixin.onResume(); + mMixin.onPause(); + + verify(mMetricsFeature, never()) + .hidden(nullable(Context.class), anyInt()); + } + + @Test + public void shouldNotLogIfMetricsCategoryIsUnknown() { + mMixin = new VisibilityLoggerMixin(METRICS_CATEGORY_UNKNOWN, mMetricsFeature); + + mMixin.onResume(); + mMixin.onPause(); + + verify(mMetricsFeature, never()) + .hidden(nullable(Context.class), anyInt()); + } + + private final class TestInstrumentable implements Instrumentable { + + public static final int TEST_METRIC = 12345; + + @Override + public int getMetricsCategory() { + return TEST_METRIC; + } + } +} diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java index afa914cf93f..741f2bc084c 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java @@ -51,7 +51,6 @@ import com.android.settings.testutils.shadow.ShadowThreadUtils; 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.CategoryManager; import com.android.settingslib.drawer.DashboardCategory; @@ -373,7 +372,7 @@ public void bindPreference_withIntentActionMetadata_shouldSetLaunchAction() { final Intent launchIntent = shadowActivity.getNextStartedActivityForResult().intent; assertThat(launchIntent.getAction()) .isEqualTo("TestAction"); - assertThat(launchIntent.getIntExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, 0)) + assertThat(launchIntent.getIntExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY, 0)) .isEqualTo(MetricsProto.MetricsEvent.SETTINGS_GESTURES); } diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java index 40e590a665c..6c663ab6cef 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java @@ -33,10 +33,10 @@ import com.android.settings.TestConfig; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.TileUtils; diff --git a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java b/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java index 1a3fa5e7092..d077e6fcd77 100644 --- a/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/conditional/ConditionTest.java @@ -22,7 +22,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java index 8b3c7708230..9ab88d3a805 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java @@ -60,7 +60,6 @@ public void setUp() { @Test public void resumePause_shouldListenUnlistenDataStateChange() { - mDataUsageList.onAttach(mContext); mDataUsageList.onResume(); verify(mListener).setListener(true, 0, mContext); diff --git a/tests/robotests/src/com/android/settings/datetime/ZonePickerTest.java b/tests/robotests/src/com/android/settings/datetime/ZonePickerTest.java index 9f6d0ef984a..92807e9fcf3 100644 --- a/tests/robotests/src/com/android/settings/datetime/ZonePickerTest.java +++ b/tests/robotests/src/com/android/settings/datetime/ZonePickerTest.java @@ -28,8 +28,8 @@ import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.testutils.shadow.ShadowZoneGetter; -import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarControllerTest.java index ab32fa28705..66ccc6ec3dd 100644 --- a/tests/robotests/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/deletionhelper/AutomaticStorageManagerSwitchBarControllerTest.java @@ -31,11 +31,11 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; import com.android.settings.widget.SwitchBar; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java index 9c566113149..90ce3953ee3 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceControllerTest.java @@ -37,13 +37,13 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.RoSystemProperties; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.deletionhelper.ActivationWarningFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowSystemProperties; import com.android.settings.widget.MasterSwitchPreference; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.After; import org.junit.Before; diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java index 2da756f5a1d..1a3139d509c 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java @@ -50,13 +50,13 @@ import com.android.settings.SubSettings; import com.android.settings.TestConfig; import com.android.settings.applications.manageapplications.ManageApplications; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.deviceinfo.PrivateVolumeSettings; import com.android.settings.deviceinfo.StorageItemPreference; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settingslib.applications.StorageStatsSource; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.deviceinfo.StorageVolumeProvider; import org.junit.After; diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java index e251be0db6b..6ad37ce2cec 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceControllerTest.java @@ -38,10 +38,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.SettingsShadowResources; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.deviceinfo.StorageVolumeProvider; import org.junit.After; diff --git a/tests/robotests/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceControllerTest.java index c003f176613..e1ce6945c13 100644 --- a/tests/robotests/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/AmbientDisplayNotificationsPreferenceControllerTest.java @@ -32,11 +32,11 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.search.InlinePayload; import com.android.settings.search.InlineSwitchPayload; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowSecureSettings; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java index 5418ead24d4..c590fd34dc1 100644 --- a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java +++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollEnrollingTest.java @@ -36,7 +36,6 @@ import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.password.ChooseLockSettingsHelper; -import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowUtils; import com.android.settings.testutils.shadow.ShadowVibrator; @@ -70,15 +69,12 @@ public class FingerprintEnrollEnrollingTest { private FingerprintEnrollEnrolling mActivity; - private FakeFeatureFactory mFactory; - @Before public void setUp() { MockitoAnnotations.initMocks(this); ShadowUtils.setFingerprintManager(mFingerprintManager); ShadowVibrator.addToServiceMap(); - mFactory = FakeFeatureFactory.setupForTest(); mActivity = Robolectric.buildActivity( FingerprintEnrollEnrolling.class, new Intent() diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java index be53aa57487..d495b74e903 100644 --- a/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java +++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintEnrollFindSensorTest.java @@ -33,7 +33,6 @@ import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.password.ChooseLockSettingsHelper; -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.ShadowEventLogWriter; @@ -70,13 +69,10 @@ public class FingerprintEnrollFindSensorTest { private FingerprintEnrollFindSensor mActivity; - private FakeFeatureFactory mFactory; - @Before public void setUp() { MockitoAnnotations.initMocks(this); ShadowUtils.setFingerprintManager(mFingerprintManager); - mFactory = FakeFeatureFactory.setupForTest(); mActivity = Robolectric.buildActivity( FingerprintEnrollFindSensor.class, diff --git a/tests/robotests/src/com/android/settings/fingerprint/FingerprintSuggestionActivityTest.java b/tests/robotests/src/com/android/settings/fingerprint/FingerprintSuggestionActivityTest.java index f52f437414b..0254bcb997b 100644 --- a/tests/robotests/src/com/android/settings/fingerprint/FingerprintSuggestionActivityTest.java +++ b/tests/robotests/src/com/android/settings/fingerprint/FingerprintSuggestionActivityTest.java @@ -30,7 +30,6 @@ import com.android.settings.R; import com.android.settings.TestConfig; -import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowEventLogWriter; import com.android.settings.testutils.shadow.ShadowLockPatternUtils; @@ -64,12 +63,9 @@ public class FingerprintSuggestionActivityTest { private ActivityController mController; - private FakeFeatureFactory mFactory; - @Before public void setUp() { MockitoAnnotations.initMocks(this); - mFactory = FakeFeatureFactory.setupForTest(); final Intent intent = new Intent(); mController = Robolectric.buildActivity(FingerprintSuggestionActivity.class, intent); diff --git a/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensorTest.java b/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensorTest.java index c786608386a..c3899e9b01d 100644 --- a/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensorTest.java +++ b/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensorTest.java @@ -27,7 +27,6 @@ import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.password.ChooseLockSettingsHelper; -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.ShadowEventLogWriter; @@ -62,13 +61,10 @@ public class SetupFingerprintEnrollFindSensorTest { private SetupFingerprintEnrollFindSensor mActivity; - private FakeFeatureFactory mFactory; - @Before public void setUp() { MockitoAnnotations.initMocks(this); ShadowUtils.setFingerprintManager(mFingerprintManager); - mFactory = FakeFeatureFactory.setupForTest(); } private void createActivity(Intent intent) { diff --git a/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java b/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java index f5b0c8a9ad0..2d98bf44511 100644 --- a/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java +++ b/tests/robotests/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroductionTest.java @@ -35,7 +35,6 @@ import com.android.settings.password.SetupChooseLockGeneric.SetupChooseLockGenericFragment; import com.android.settings.password.SetupSkipDialog; import com.android.settings.password.StorageManagerWrapper; -import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowEventLogWriter; import com.android.settings.testutils.shadow.ShadowFingerprintManager; @@ -73,7 +72,6 @@ public class SetupFingerprintEnrollIntroductionTest { @Mock private UserInfo mUserInfo; - private FakeFeatureFactory mFactory; private ActivityController mController; @@ -85,8 +83,6 @@ public void setUp() { .setSystemFeature(PackageManager.FEATURE_FINGERPRINT, true); ShadowFingerprintManager.addToServiceMap(); - mFactory = FakeFeatureFactory.setupForTest(); - final Intent intent = new Intent(); mController = Robolectric.buildActivity(SetupFingerprintEnrollIntroduction.class, intent); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java index 3e33823b82c..38391c9350b 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java @@ -24,6 +24,7 @@ import android.util.Pair; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.fuelgauge.anomaly.action.StopAndBackgroundCheckAction; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; @@ -31,7 +32,6 @@ import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector; import com.android.settings.testutils.shadow.ShadowKeyValueListParserWrapperImpl; import com.android.settings.fuelgauge.anomaly.checker.WakeupAlarmAnomalyDetector; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java index 737c16d5001..1ee52ca463c 100644 --- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java +++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java @@ -20,7 +20,6 @@ import android.view.View; import android.widget.TextView; import com.android.settings.TestConfig; -import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowSettingsPreferenceFragment; @@ -28,7 +27,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; @@ -41,11 +39,6 @@ public class LocaleListEditorTest { private LocaleListEditor mLocaleListEditor; - @Mock - private Context mContext; - - private FakeFeatureFactory mFactory; - @Before public void setUp() { mLocaleListEditor = new LocaleListEditor(); @@ -55,13 +48,11 @@ public void setUp() { RuntimeEnvironment.application.getSystemService(Context.RESTRICTIONS_SERVICE)); ReflectionHelpers.setField(mLocaleListEditor, "mUserManager", RuntimeEnvironment.application.getSystemService(Context.USER_SERVICE)); - mFactory = FakeFeatureFactory.setupForTest(); } @Test public void testDisallowConfigLocale_unrestrict() { ReflectionHelpers.setField(mLocaleListEditor, "mIsUiRestricted", true); - mLocaleListEditor.onAttach(mContext); mLocaleListEditor.onResume(); Assert.assertEquals(View.GONE, mLocaleListEditor.getEmptyTextView().getVisibility()); } @@ -69,7 +60,6 @@ public void testDisallowConfigLocale_unrestrict() { @Test public void testDisallowConfigLocale_restrict() { ReflectionHelpers.setField(mLocaleListEditor, "mIsUiRestricted", false); - mLocaleListEditor.onAttach(mContext); mLocaleListEditor.onResume(); Assert.assertEquals(View.VISIBLE, mLocaleListEditor.getEmptyTextView().getVisibility()); } diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index 3325332703b..752ca1e21dc 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -24,6 +24,7 @@ import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.bluetooth.BluetoothFeatureProvider; import com.android.settings.connecteddevice.SmsMirroringFeatureProvider; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.datausage.DataPlanFeatureProvider; @@ -38,7 +39,6 @@ import com.android.settings.security.SecurityFeatureProvider; import com.android.settings.slices.SlicesFeatureProvider; import com.android.settings.users.UserFeatureProvider; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.mockito.Answers; diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEventLogWriter.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEventLogWriter.java index 9caf09f3e8d..dcced4e40d0 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEventLogWriter.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowEventLogWriter.java @@ -19,7 +19,7 @@ import android.content.Context; -import com.android.settingslib.core.instrumentation.EventLogWriter; +import com.android.settings.core.instrumentation.EventLogWriter; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java index 59a08ae8801..e44be0e8781 100644 --- a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java +++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java @@ -44,10 +44,10 @@ import com.android.settings.TestConfig; import com.android.settings.applications.defaultapps.DefaultAppInfo; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.widget.RadioButtonPreference; import com.android.settings.wrapper.UserPackageWrapper; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.wrapper.PackageManagerWrapper; import org.junit.Before; diff --git a/tests/robotests/src/com/android/settings/wifi/WifiEnablerTest.java b/tests/robotests/src/com/android/settings/wifi/WifiEnablerTest.java index 84549a62388..63f89e62c5c 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiEnablerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiEnablerTest.java @@ -22,9 +22,9 @@ import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.widget.SwitchWidgetController; import com.android.settings.wrapper.ConnectivityManagerWrapper; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java index 82569c74054..1708e364e0f 100644 --- a/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/WifiMasterSwitchPreferenceControllerTest.java @@ -30,10 +30,10 @@ import android.support.v7.preference.PreferenceScreen; import com.android.settings.TestConfig; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.widget.MasterSwitchPreference; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; import org.junit.Test; diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java index ca2cac09257..4f774357612 100644 --- a/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java @@ -65,6 +65,7 @@ import com.android.settings.R; import com.android.settings.TestConfig; import com.android.settings.applications.LayoutPreference; +import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowBidiFormatter; import com.android.settings.testutils.shadow.ShadowDevicePolicyManagerWrapper; @@ -75,7 +76,6 @@ import com.android.settings.widget.EntityHeaderController; import com.android.settings.wifi.WifiDetailPreference; import com.android.settings.wrapper.ConnectivityManagerWrapper; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.wifi.AccessPoint; From e2ad4149abd8e08e389d22b5bbff4560400ca5ec Mon Sep 17 00:00:00 2001 From: Carlos Valdivia Date: Tue, 23 Jan 2018 09:12:59 -0800 Subject: [PATCH 10/28] AF/FR Fix onActivityResult condition. Chase list bug. Accidentally bailed on true effectively. Manual tested by going to Settings > System > Erase All Data And then hitting then successfully wiping and reseting. Test: make RunSettingsRoboTests -j40 Bug: 72324059 Change-Id: Ib2a155820811f0ec62a45c1312475c24646ede76 (cherry picked from commit 5dd6ed470eb8b3c59c88f605e96789da2f147cc4) --- src/com/android/settings/MasterClear.java | 11 ++++++++--- .../src/com/android/settings/MasterClearTest.java | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 3cc722bbffa..b24bd62d248 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -75,8 +75,8 @@ public class MasterClear extends InstrumentedPreferenceFragment { private static final String TAG = "MasterClear"; - private static final int KEYGUARD_REQUEST = 55; - private static final int CREDENTIAL_CONFIRM_REQUEST = 56; + @VisibleForTesting static final int KEYGUARD_REQUEST = 55; + @VisibleForTesting static final int CREDENTIAL_CONFIRM_REQUEST = 56; static final String ERASE_EXTERNAL_EXTRA = "erase_sd"; static final String ERASE_ESIMS_EXTRA = "erase_esim"; @@ -113,11 +113,16 @@ private boolean runKeyguardConfirmation(int request) { request, res.getText(R.string.master_clear_title)); } + @VisibleForTesting + boolean isValidRequestCode(int requestCode) { + return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST)); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode != KEYGUARD_REQUEST || requestCode != CREDENTIAL_CONFIRM_REQUEST) { + if (!isValidRequestCode(requestCode)) { return; } diff --git a/tests/robotests/src/com/android/settings/MasterClearTest.java b/tests/robotests/src/com/android/settings/MasterClearTest.java index 361bc8f319d..c23e88facbe 100644 --- a/tests/robotests/src/com/android/settings/MasterClearTest.java +++ b/tests/robotests/src/com/android/settings/MasterClearTest.java @@ -167,6 +167,13 @@ public void testTryShowAccountConfirmation_unsupported() { assertThat(mMasterClear.tryShowAccountConfirmation()).isFalse(); } + @Test + public void testIsValidRequestCode() { + assertThat(mMasterClear.isValidRequestCode(MasterClear.KEYGUARD_REQUEST)).isTrue(); + assertThat(mMasterClear.isValidRequestCode(MasterClear.CREDENTIAL_CONFIRM_REQUEST)).isTrue(); + assertThat(mMasterClear.isValidRequestCode(0)).isFalse(); + } + private void initScrollView(int height, int scrollY, int childBottom) { when(mScrollView.getHeight()).thenReturn(height); when(mScrollView.getScrollY()).thenReturn(scrollY); From 7dfdd888d611671c498a714f47277dbbd028a65e Mon Sep 17 00:00:00 2001 From: Carlos Valdivia Date: Tue, 23 Jan 2018 09:12:59 -0800 Subject: [PATCH 11/28] AF/FR Fix onActivityResult condition. Chase list bug. Accidentally bailed on true effectively. Manual tested by going to Settings > System > Erase All Data And then hitting then successfully wiping and reseting. Test: make RunSettingsRoboTests -j40 Bug: 72324059 Change-Id: Ib2a155820811f0ec62a45c1312475c24646ede76 (cherry picked from commit 5dd6ed470eb8b3c59c88f605e96789da2f147cc4) --- src/com/android/settings/MasterClear.java | 9 +++++++-- .../src/com/android/settings/MasterClearTest.java | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 0e337f68bf0..b7fb69404cd 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -75,7 +75,7 @@ public class MasterClear extends InstrumentedPreferenceFragment { private static final String TAG = "MasterClear"; - private static final int KEYGUARD_REQUEST = 55; + @VisibleForTesting static final int KEYGUARD_REQUEST = 55; @VisibleForTesting static final int CREDENTIAL_CONFIRM_REQUEST = 56; private static final String KEY_SHOW_ESIM_RESET_CHECKBOX @@ -118,11 +118,16 @@ private boolean runKeyguardConfirmation(int request) { request, res.getText(R.string.master_clear_title)); } + @VisibleForTesting + boolean isValidRequestCode(int requestCode) { + return !((requestCode != KEYGUARD_REQUEST) && (requestCode != CREDENTIAL_CONFIRM_REQUEST)); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode != KEYGUARD_REQUEST || requestCode != CREDENTIAL_CONFIRM_REQUEST) { + if (!isValidRequestCode(requestCode)) { return; } diff --git a/tests/robotests/src/com/android/settings/MasterClearTest.java b/tests/robotests/src/com/android/settings/MasterClearTest.java index ac753c109f2..9bf3310c6b8 100644 --- a/tests/robotests/src/com/android/settings/MasterClearTest.java +++ b/tests/robotests/src/com/android/settings/MasterClearTest.java @@ -270,6 +270,13 @@ public void testTryShowAccountConfirmation_ok() { assertThat(mMasterClear.tryShowAccountConfirmation()).isTrue(); } + @Test + public void testIsValidRequestCode() { + assertThat(mMasterClear.isValidRequestCode(MasterClear.KEYGUARD_REQUEST)).isTrue(); + assertThat(mMasterClear.isValidRequestCode(MasterClear.CREDENTIAL_CONFIRM_REQUEST)).isTrue(); + assertThat(mMasterClear.isValidRequestCode(0)).isFalse(); + } + private void initScrollView(int height, int scrollY, int childBottom) { when(mScrollView.getHeight()).thenReturn(height); when(mScrollView.getScrollY()).thenReturn(scrollY); From f98425c87454509f05790b008a1e54775150f2e3 Mon Sep 17 00:00:00 2001 From: Salvador Martinez Date: Wed, 24 Jan 2018 10:24:04 -0800 Subject: [PATCH 12/28] Fix null pointer from PowerUsageFeatureProvider Some locations did not check if the returned value was null before doing operations on them and could crash. This CL changes those spots to take that into account. Test: b/72463854 will add in follow up to unblock dogfood Bug: 72350595 Change-Id: I0ace5c0ab4a8aa9fd5b09d41d6f986143246f059 (cherry picked from commit 250a79830cd065602b99ec3d611da694ee294ea8) --- .../settings/fuelgauge/BatteryInfo.java | 24 ++++++++++--------- .../fuelgauge/DebugEstimatesLoader.java | 3 +++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index c4c795b29b1..63841307ed8 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -171,18 +171,20 @@ protected BatteryInfo doInBackground(Void... params) { if (discharging && provider != null && provider.isEnhancedBatteryPredictionEnabled(context)) { Estimate estimate = provider.getEnhancedBatteryPrediction(context); - BatteryUtils.logRuntime(LOG_TAG, "time for enhanced BatteryInfo", startTime); - return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, - elapsedRealtimeUs, shortString, - BatteryUtils.convertMsToUs(estimate.estimateMillis), - estimate.isBasedOnUsage); - } else { - long prediction = discharging - ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0; - BatteryUtils.logRuntime(LOG_TAG, "time for regular BatteryInfo", startTime); - return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, - elapsedRealtimeUs, shortString, prediction, false); + if(estimate != null) { + BatteryUtils + .logRuntime(LOG_TAG, "time for enhanced BatteryInfo", startTime); + return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, + elapsedRealtimeUs, shortString, + BatteryUtils.convertMsToUs(estimate.estimateMillis), + estimate.isBasedOnUsage); + } } + long prediction = discharging + ? stats.computeBatteryTimeRemaining(elapsedRealtimeUs) : 0; + BatteryUtils.logRuntime(LOG_TAG, "time for regular BatteryInfo", startTime); + return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, + elapsedRealtimeUs, shortString, prediction, false); } @Override diff --git a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java index a080e2b7300..e58ccd339a0 100644 --- a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java +++ b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java @@ -55,6 +55,9 @@ public List loadInBackground() { stats, elapsedRealtimeUs, false); Estimate estimate = powerUsageFeatureProvider.getEnhancedBatteryPrediction(context); + if (estimate == null) { + estimate = new Estimate(0, false); + } BatteryInfo newInfo = BatteryInfo.getBatteryInfo(getContext(), batteryBroadcast, stats, elapsedRealtimeUs, false, BatteryUtils.convertMsToUs(estimate.estimateMillis), From 94cf4c39eaf78dfa73373a1504d5a7f2dc634ef4 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Wed, 24 Jan 2018 16:14:16 -0800 Subject: [PATCH 13/28] Prevent crash in recent app list In some situations if you have recently used an app and then uninstalled it, the display of recent apps in Settings->Apps & notifications would crash. This is due to a bug in recent CL that fixed b/71591298. Test: make -j64 RunSettingsRoboTests Change-Id: Id04b073b2617eeff0e188b10d3743496a9f70d5e Fixes: 72340364 (cherry picked from commit e04101be07b2225ee51f474d8f254d892fc4c6bc) --- .../RecentAppsPreferenceController.java | 2 +- ...centNotifyingAppsPreferenceController.java | 2 +- .../RecentAppsPreferenceControllerTest.java | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java index 20c5581ae8e..38590817ae7 100644 --- a/src/com/android/settings/applications/RecentAppsPreferenceController.java +++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java @@ -325,7 +325,7 @@ private boolean shouldIncludePkgInRecents(UsageStats stat) { // Not visible on launcher -> likely not a user visible app, skip if non-instant. final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry(pkgName, mUserId); - if (!AppUtils.isInstant(appEntry.info)) { + if (appEntry == null || appEntry.info == null || !AppUtils.isInstant(appEntry.info)) { Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName); return false; } diff --git a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java index ef34a9b65e6..dbffc550d5e 100644 --- a/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java +++ b/src/com/android/settings/notification/RecentNotifyingAppsPreferenceController.java @@ -283,7 +283,7 @@ private boolean shouldIncludePkgInRecents(String pkgName) { // Not visible on launcher -> likely not a user visible app, skip if non-instant. final ApplicationsState.AppEntry appEntry = mApplicationsState.getEntry(pkgName, mUserId); - if (!AppUtils.isInstant(appEntry.info)) { + if (appEntry == null || appEntry.info == null || !AppUtils.isInstant(appEntry.info)) { Log.d(TAG, "Not a user visible or instant app, skipping " + pkgName); return false; } diff --git a/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java index bd57211a231..ed97fe774b6 100644 --- a/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/RecentAppsPreferenceControllerTest.java @@ -247,6 +247,33 @@ public void display_showRecentsWithInstantApp() { assertThat(prefs.get(0).getKey()).isEqualTo(stat2.mPackageName); } + @Test + public void display_showRecentsWithNullAppEntryOrInfo() { + final List stats = new ArrayList<>(); + final UsageStats stat1 = new UsageStats(); + final UsageStats stat2 = new UsageStats(); + stat1.mLastTimeUsed = System.currentTimeMillis(); + stat1.mPackageName = "pkg.class"; + stats.add(stat1); + + stat2.mLastTimeUsed = System.currentTimeMillis(); + stat2.mPackageName = "pkg.class2"; + stats.add(stat2); + + // app1 has AppEntry with null info, app2 has null AppEntry. + mAppEntry.info = null; + when(mAppState.getEntry(stat1.mPackageName, UserHandle.myUserId())) + .thenReturn(mAppEntry); + when(mAppState.getEntry(stat2.mPackageName, UserHandle.myUserId())) + .thenReturn(null); + + when(mUsageStatsManager.queryUsageStats(anyInt(), anyLong(), anyLong())) + .thenReturn(stats); + + // We should not crash here. + mController.displayPreference(mScreen); + } + @Test public void display_hasRecentButNoneDisplayable_showAppInfo() { final List stats = new ArrayList<>(); From 78d21f602a4e3ebf6889b88c819e7c000c4ee6a0 Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Tue, 27 Feb 2018 20:12:57 +0000 Subject: [PATCH 14/28] Revert "Update to match now slice APIs" This reverts commit 2d96005aba3a509fc801f9a05d61786d8ad78218. Reason for revert: prebuilt revert Bug: 73903252 Bug: 73876473 Bug: 73875529 Bug: 73866916 Change-Id: I5d47a7c5ddebc16c1c508f5bb48ace809cbaf273 (cherry picked from commit 80c806abd60f8d8381f6654fd2f708f5d915f0bf) --- src/com/android/settings/slices/SettingsSliceProvider.java | 6 ++---- src/com/android/settings/slices/SliceBuilderUtils.java | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index d8ba991a145..433bdf368bd 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -35,7 +35,6 @@ import androidx.app.slice.Slice; import androidx.app.slice.SliceProvider; -import androidx.app.slice.builders.SliceAction; import androidx.app.slice.builders.ListBuilder; /** @@ -174,9 +173,8 @@ private Slice createWifiSlice(Uri sliceUri) { .setTitle(getContext().getString(R.string.wifi_settings)) .setTitleItem(Icon.createWithResource(getContext(), R.drawable.wifi_signal)) .setSubtitle(state) - .addEndItem(new SliceAction(getBroadcastIntent(ACTION_WIFI_CHANGED), - null, finalWifiEnabled)) - .setPrimaryAction(new SliceAction(getIntent(Intent.ACTION_MAIN), null, null))) + .addToggle(getBroadcastIntent(ACTION_WIFI_CHANGED), finalWifiEnabled) + .setContentIntent(getIntent(Intent.ACTION_MAIN))) .build(); } diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index 3df24edbeb1..a01ea1bd0ed 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -34,7 +34,6 @@ import com.android.settingslib.core.AbstractPreferenceController; import androidx.app.slice.Slice; -import androidx.app.slice.builders.SliceAction; import androidx.app.slice.builders.ListBuilder; import androidx.app.slice.builders.ListBuilder.RowBuilder; @@ -64,7 +63,7 @@ public static Slice buildSlice(Context context, SliceData sliceData) { .setTitle(sliceData.getTitle()) .setTitleItem(icon) .setSubtitle(subtitleText) - .setPrimaryAction(new SliceAction(contentIntent, null, null)); + .setContentIntent(contentIntent); // TODO (b/71640747) Respect setting availability. @@ -101,7 +100,7 @@ private static void addToggleAction(Context context, RowBuilder builder, boolean String key) { PendingIntent actionIntent = getActionIntent(context, SettingsSliceProvider.ACTION_TOGGLE_CHANGED, key); - builder.addEndItem(new SliceAction(actionIntent, null, isChecked)); + builder.addToggle(actionIntent, isChecked); } private static PendingIntent getActionIntent(Context context, String action, String key) { From 5ae1758d47dc706a8c1c66829df416ec3e236df7 Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Mon, 5 Mar 2018 16:13:33 +0000 Subject: [PATCH 15/28] Fix SettingsRoboTests build error with OpenJDK 9 - CL http://ag/3671365 broke git_pi-release/marlin-userdebug-jdk9 when building "m checkbuild". It shouldn't break the image build Test: m SettingsRoboTests ROBOTEST_FILTER=com.android.settings.datetime.timezone EXPERIMENTAL_USE_OPENJDK9=true USE_R8=true Change-Id: I161c0350cff55bd13ba4a6c63df4e4e9bc4b1a5f (cherry picked from commit e10875210adef2949aa4f9b56c40fb6d81b0f222) --- .../timezone/model/TimeZoneDataTest.java | 6 +-- .../src/libcore/util/CountryTimeZones.java | 45 ------------------- .../src/libcore/util/CountryZonesFinder.java | 38 ---------------- 3 files changed, 3 insertions(+), 86 deletions(-) delete mode 100644 tests/robotests/src/libcore/util/CountryTimeZones.java delete mode 100644 tests/robotests/src/libcore/util/CountryZonesFinder.java diff --git a/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java b/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java index e6073a8ecb4..4a624b70f6c 100644 --- a/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java +++ b/tests/robotests/src/com/android/settings/datetime/timezone/model/TimeZoneDataTest.java @@ -73,13 +73,13 @@ public void testLookupCountryCodesForZoneId() { CountryTimeZones US = mock(CountryTimeZones.class); when(US.getCountryIso()).thenReturn("us"); when(US.getTimeZoneMappings()).thenReturn(Arrays.asList( - new CountryTimeZones.TimeZoneMapping("Unknown/Secret_City", true), - new CountryTimeZones.TimeZoneMapping("Unknown/Secret_City2", false) + TimeZoneMapping.createForTests("Unknown/Secret_City", true), + TimeZoneMapping.createForTests("Unknown/Secret_City2", false) )); CountryTimeZones GB = mock(CountryTimeZones.class); when(GB.getCountryIso()).thenReturn("gb"); when(GB.getTimeZoneMappings()).thenReturn(Collections.singletonList( - new TimeZoneMapping("Unknown/Secret_City", true) + TimeZoneMapping.createForTests("Unknown/Secret_City", true) )); when(mCountryZonesFinder.lookupCountryTimeZonesForZoneId("Unknown/Secret_City")) .thenReturn(Arrays.asList(US, GB)); diff --git a/tests/robotests/src/libcore/util/CountryTimeZones.java b/tests/robotests/src/libcore/util/CountryTimeZones.java deleted file mode 100644 index 2087848609d..00000000000 --- a/tests/robotests/src/libcore/util/CountryTimeZones.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 libcore.util; - -import java.util.List; - -/** - * Empty implementation of CountryTimeZones for Robolectric test. - */ -public class CountryTimeZones { - public CountryTimeZones() { - } - - public final static class TimeZoneMapping { - public final String timeZoneId; - public final boolean showInPicker; - - public TimeZoneMapping(String timeZoneId, boolean showInPicker) { - this.timeZoneId = timeZoneId; - this.showInPicker = showInPicker; - } - } - - public List getTimeZoneMappings() { - return null; - } - - public String getCountryIso() { - return null; - } -} diff --git a/tests/robotests/src/libcore/util/CountryZonesFinder.java b/tests/robotests/src/libcore/util/CountryZonesFinder.java deleted file mode 100644 index 51149ecb915..00000000000 --- a/tests/robotests/src/libcore/util/CountryZonesFinder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 libcore.util; - -import java.util.List; - -/** - * Empty implementation of CountryZonesFinder for Robolectric test. - */ -public class CountryZonesFinder { - public CountryZonesFinder(List countryTimeZonesList) {} - - public List lookupAllCountryIsoCodes() { - return null; - } - - public List lookupCountryTimeZonesForZoneId(String zoneId) { - return null; - } - - public CountryTimeZones lookupCountryTimeZones(String countryIso) { - return null; - } -} From cd7e9e6e6cc7868b76cd813fb37cfd9251ff1059 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Thu, 29 Mar 2018 20:20:08 +0000 Subject: [PATCH 16/28] Revert "Support AOD in the Universal Settings API" This reverts commit 537c2cfc2c21ffee691b11864a26bd84f043916a. Fixes: 77278897 Change-Id: I7a5ff34bf79b87f6a7be50c2e8f421bfc4a22195 (cherry picked from commit f75edca09fd5b576a0b733dc1b699f4fd8f05752) --- res/xml/ambient_display_settings.xml | 3 +- ...ntDisplayAlwaysOnPreferenceController.java | 58 ++++++++------ .../display/AmbientDisplaySettings.java | 30 ++----- ...splayAlwaysOnPreferenceControllerTest.java | 80 +++++++++++-------- .../display/AmbientDisplaySettingsTest.java | 69 ---------------- 5 files changed, 89 insertions(+), 151 deletions(-) delete mode 100644 tests/robotests/src/com/android/settings/display/AmbientDisplaySettingsTest.java diff --git a/res/xml/ambient_display_settings.xml b/res/xml/ambient_display_settings.xml index 037421cd213..306ead5d50f 100644 --- a/res/xml/ambient_display_settings.xml +++ b/res/xml/ambient_display_settings.xml @@ -29,8 +29,7 @@ + android:summary="@string/doze_always_on_summary" /> buildPreferenceControllers(Context context, Lifecycle lifecycle, AmbientDisplayConfiguration config, - MetricsFeatureProvider metricsFeatureProvider) { + MetricsFeatureProvider metricsFeatureProvider, + AmbientDisplayAlwaysOnPreferenceController.OnPreferenceChangedCallback aodCallback) { final List controllers = new ArrayList<>(); controllers.add(new AmbientDisplayNotificationsPreferenceController(context, config, metricsFeatureProvider)); + controllers.add(new AmbientDisplayAlwaysOnPreferenceController(context, config, + aodCallback)); controllers.add(new DoubleTapScreenPreferenceController(context, lifecycle, config, MY_USER_ID, KEY_AMBIENT_DISPLAY_DOUBLE_TAP)); controllers.add(new PickupGesturePreferenceController(context, lifecycle, config, @@ -64,14 +64,6 @@ private static List buildPreferenceControllers(Con return controllers; } - @Override - public void onAttach(Context context) { - super.onAttach(context); - final AmbientDisplayAlwaysOnPreferenceController controller = use( - AmbientDisplayAlwaysOnPreferenceController.class); - controller.setConfig(getConfig(context)); - controller.setCallback(this::updatePreferenceStates); - } @Override protected String getLogTag() { @@ -86,7 +78,8 @@ protected int getPreferenceScreenResId() { @Override protected List createPreferenceControllers(Context context) { return buildPreferenceControllers(context, getLifecycle(), - getConfig(context), mMetricsFeatureProvider); + new AmbientDisplayConfiguration(context), mMetricsFeatureProvider, + this::updatePreferenceStates); } @Override @@ -111,14 +104,7 @@ public List getXmlResourcesToIndex(Context context, public List createPreferenceControllers( Context context) { return buildPreferenceControllers(context, null, - new AmbientDisplayConfiguration(context), null); + new AmbientDisplayConfiguration(context), null, null); } }; - - private AmbientDisplayConfiguration getConfig(Context context) { - if (mConfig != null) { - mConfig = new AmbientDisplayConfiguration(context); - } - return mConfig; - } } diff --git a/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java index e1c4b70477a..d06ea2ac50c 100644 --- a/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceControllerTest.java @@ -17,14 +17,17 @@ package com.android.settings.display; import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.settings.search.InlinePayload; @@ -46,6 +49,8 @@ public class AmbientDisplayAlwaysOnPreferenceControllerTest { @Mock private AmbientDisplayConfiguration mConfig; + @Mock + private SwitchPreference mSwitchPreference; private Context mContext; @@ -59,90 +64,95 @@ public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mContentResolver = mContext.getContentResolver(); - mController = new AmbientDisplayAlwaysOnPreferenceController(mContext, "key"); - mController.setConfig(mConfig); - mController.setCallback(() -> mCallbackInvoked = true); + mController = new AmbientDisplayAlwaysOnPreferenceController(mContext, mConfig, + () -> { + mCallbackInvoked = true; + }); } @Test - public void getAvailabilityStatus_available() { - when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(true); + public void updateState_enabled() { + when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true); - assertThat(mController.getAvailabilityStatus()).isEqualTo( - AmbientDisplayAlwaysOnPreferenceController.AVAILABLE); + mController.updateState(mSwitchPreference); + + verify(mSwitchPreference).setChecked(true); } @Test - public void getAvailabilityStatus_disabled_unsupported() { - when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(false); + public void updateState_disabled() { + when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false); - assertThat(mController.getAvailabilityStatus()).isEqualTo( - AmbientDisplayAlwaysOnPreferenceController.DISABLED_UNSUPPORTED); + mController.updateState(mSwitchPreference); + + verify(mSwitchPreference).setChecked(false); } @Test - public void isChecked_enabled() { - when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true); - - assertThat(mController.isChecked()).isTrue(); + public void onPreferenceChange_callback() { + assertThat(mCallbackInvoked).isFalse(); + mController.onPreferenceChange(mSwitchPreference, true); + assertThat(mCallbackInvoked).isTrue(); } @Test - public void isChecked_disabled() { - when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false); + public void onPreferenceChange_enable() { + mController.onPreferenceChange(mSwitchPreference, true); - assertThat(mController.isChecked()).isFalse(); + assertThat(Settings.Secure.getInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, -1)) + .isEqualTo(1); } @Test - public void setChecked_enabled() { - mController.setChecked(true); + public void onPreferenceChange_disable() { + mController.onPreferenceChange(mSwitchPreference, false); assertThat(Settings.Secure.getInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, -1)) - .isEqualTo(1); + .isEqualTo(0); } @Test - public void setChecked_disabled() { - mController.setChecked(false); + public void isAvailable_available() { + mController = spy(mController); + doReturn(true).when(mController).alwaysOnAvailableForUser(any()); - assertThat(Settings.Secure.getInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, -1)) - .isEqualTo(0); + assertThat(mController.isAvailable()).isTrue(); } @Test - public void onPreferenceChange_callback() { - assertThat(mCallbackInvoked).isFalse(); - mController.setChecked(true); - assertThat(mCallbackInvoked).isTrue(); + public void isAvailable_unavailable() { + mController = spy(mController); + doReturn(false).when(mController).alwaysOnAvailableForUser(any()); + + assertThat(mController.isAvailable()).isFalse(); } @Test public void testPreferenceController_ProperResultPayloadType() { - when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(false); mController = spy(mController); + doReturn(false).when(mController).alwaysOnAvailableForUser(any()); assertThat(mController.getResultPayload()).isInstanceOf(InlineSwitchPayload.class); } @Test public void testSetValue_updatesCorrectly() { - when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(false); mController = spy(mController); + doReturn(false).when(mController).alwaysOnAvailableForUser(any()); final int newValue = 1; Settings.Secure.putInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, 0 /* value */); ((InlinePayload) mController.getResultPayload()).setValue(mContext, newValue); final int updatedValue = Settings.Secure. - getInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, 1 /* default */); + getInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, 1 /* default */); assertThat(updatedValue).isEqualTo(newValue); } @Test public void testGetValue_correctValueReturned() { - when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(false); mController = spy(mController); + doReturn(false).when(mController).alwaysOnAvailableForUser(any()); final int currentValue = 1; Settings.Secure.putInt(mContentResolver, Settings.Secure.DOZE_ALWAYS_ON, currentValue); diff --git a/tests/robotests/src/com/android/settings/display/AmbientDisplaySettingsTest.java b/tests/robotests/src/com/android/settings/display/AmbientDisplaySettingsTest.java deleted file mode 100644 index bab5d410e4d..00000000000 --- a/tests/robotests/src/com/android/settings/display/AmbientDisplaySettingsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2017 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.display; - -import static org.mockito.Mockito.any; -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 android.content.Context; - -import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settingslib.core.AbstractPreferenceController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; -import org.robolectric.RuntimeEnvironment; - -@RunWith(SettingsRobolectricTestRunner.class) -public class AmbientDisplaySettingsTest { - - private TestFragment mTestFragment; - - private Context mContext; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mTestFragment = spy(new TestFragment()); - } - - @Test - public void onAttach_shouldSetConfigAndCallback() { - final AmbientDisplayAlwaysOnPreferenceController controller = mock( - AmbientDisplayAlwaysOnPreferenceController.class); - doReturn(controller).when(mTestFragment).use( - AmbientDisplayAlwaysOnPreferenceController.class); - - mTestFragment.onAttach(mContext); - - verify(controller).setConfig(any()); - verify(controller).setCallback(any()); - } - - public static class TestFragment extends AmbientDisplaySettings { - @Override - protected T use(Class clazz) { - return super.use(clazz); - } - } -} \ No newline at end of file From b83242c635daa1567f5e26b5a581803b379f4076 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Wed, 23 May 2018 16:10:11 -0400 Subject: [PATCH 17/28] Follow SliceManager API changes Test: make Bug: 78898947 Change-Id: I5a46ccafe36ad2e0fdac745606d9907f07a86d0c (cherry picked from commit 20495a8c367fd793fe8f852694630a919b92184a) --- .../settings/search/DeviceIndexUpdateJobService.java | 12 ++++++------ .../search/DeviceIndexUpdateJobServiceTest.java | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/search/DeviceIndexUpdateJobService.java b/src/com/android/settings/search/DeviceIndexUpdateJobService.java index 97b0a61ca6b..3eb904119bc 100644 --- a/src/com/android/settings/search/DeviceIndexUpdateJobService.java +++ b/src/com/android/settings/search/DeviceIndexUpdateJobService.java @@ -38,8 +38,8 @@ import androidx.slice.Slice; import androidx.slice.SliceItem; -import androidx.slice.SliceManager; -import androidx.slice.SliceManager.SliceCallback; +import androidx.slice.SliceViewManager; +import androidx.slice.SliceViewManager.SliceCallback; import androidx.slice.SliceMetadata; import androidx.slice.core.SliceQuery; import androidx.slice.widget.ListContent; @@ -80,7 +80,7 @@ protected void updateIndex(JobParameters params) { } final DeviceIndexFeatureProvider indexProvider = FeatureFactory.getFactory(this) .getDeviceIndexFeatureProvider(); - final SliceManager manager = getSliceManager(); + final SliceViewManager manager = getSliceViewManager(); final Uri baseUri = new Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) @@ -124,8 +124,8 @@ protected void updateIndex(JobParameters params) { jobFinished(params, false); } - protected SliceManager getSliceManager() { - return SliceManager.getInstance(this); + protected SliceViewManager getSliceViewManager() { + return SliceViewManager.getInstance(this); } protected SliceMetadata getMetadata(Slice loadedSlice) { @@ -158,7 +158,7 @@ protected CharSequence findTitle(Slice loadedSlice, SliceMetadata metaData) { return null; } - protected Slice bindSliceSynchronous(SliceManager manager, Uri slice) { + protected Slice bindSliceSynchronous(SliceViewManager manager, Uri slice) { final Slice[] returnSlice = new Slice[1]; CountDownLatch latch = new CountDownLatch(1); SliceCallback callback = new SliceCallback() { diff --git a/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java b/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java index b5de9737db2..1c02b99323d 100644 --- a/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java +++ b/tests/robotests/src/com/android/settings/search/DeviceIndexUpdateJobServiceTest.java @@ -45,7 +45,7 @@ import java.util.List; import androidx.slice.Slice; -import androidx.slice.SliceManager; +import androidx.slice.SliceViewManager; import androidx.slice.SliceMetadata; @RunWith(SettingsRobolectricTestRunner.class) @@ -57,17 +57,17 @@ public class DeviceIndexUpdateJobServiceTest { private Activity mActivity; private DeviceIndexUpdateJobService mJob; - private SliceManager mSliceManager; + private SliceViewManager mSliceManager; @Before public void setup() { FakeFeatureFactory.setupForTest(); mActivity = spy(Robolectric.buildActivity(Activity.class).create().visible().get()); mJob = spy(new DeviceIndexUpdateJobService()); - mSliceManager = mock(SliceManager.class); + mSliceManager = mock(SliceViewManager.class); doReturn(mActivity.getPackageName()).when(mJob).getPackageName(); - doReturn(mSliceManager).when(mJob).getSliceManager(); + doReturn(mSliceManager).when(mJob).getSliceViewManager(); doNothing().when(mJob).jobFinished(null, false); } @@ -144,4 +144,4 @@ private void setSlices(Slice... slice) { when(mSliceManager.getSliceDescendants(BASE_URI)).thenReturn(mUris); } -} \ No newline at end of file +} From 23059f5e5aa1a973b4dcc2bcadc6432d3db931f2 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 30 May 2018 09:50:52 -0700 Subject: [PATCH 18/28] Skip device index job if caller is not Settings app. Change-Id: I96184f111e83477b46ddf321ec74917bab330048 Fixes: 80437512 Fixes: 109713077 Test: robotests (cherry picked from commit 74ba1a51d74a4fb7fc2ccd62fd38087974c20020) (cherry picked from commit 9ecf3e0c1110e6900f63a600e483f8925e366b4c) --- .../search/DeviceIndexFeatureProvider.java | 30 +++++++++-- .../DeviceIndexFeatureProviderTest.java | 50 +++++++++++++------ 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/search/DeviceIndexFeatureProvider.java b/src/com/android/settings/search/DeviceIndexFeatureProvider.java index c4d3abfcaa8..e6b3e937b85 100644 --- a/src/com/android/settings/search/DeviceIndexFeatureProvider.java +++ b/src/com/android/settings/search/DeviceIndexFeatureProvider.java @@ -21,7 +21,10 @@ import android.app.job.JobScheduler; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.net.Uri; +import android.os.Binder; import android.os.Build; import android.provider.Settings; import android.text.TextUtils; @@ -36,7 +39,6 @@ public interface DeviceIndexFeatureProvider { - String TAG = "DeviceIndex"; String INDEX_VERSION = "settings:index_version"; @@ -57,7 +59,7 @@ void index(Context context, CharSequence title, Uri sliceUri, Uri launchUri, default void updateIndex(Context context, boolean force) { if (!isIndexingEnabled()) { - Log.w(TAG, "Skipping: device index is not enabled"); + Log.i(TAG, "Skipping: device index is not enabled"); return; } @@ -66,7 +68,29 @@ default void updateIndex(Context context, boolean force) { return; } + final ComponentName jobComponent = new ComponentName(context.getPackageName(), + DeviceIndexUpdateJobService.class.getName()); + + try { + final int callerUid = Binder.getCallingUid(); + final ServiceInfo si = context.getPackageManager().getServiceInfo(jobComponent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + if (si == null) { + Log.w(TAG, "Skipping: No such service " + jobComponent); + return; + } + if (si.applicationInfo.uid != callerUid) { + Log.w(TAG, "Skipping: Uid cannot schedule DeviceIndexUpdate: " + callerUid); + return; + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Skipping: error finding DeviceIndexUpdateJobService from packageManager"); + return; + } + if (!force && skipIndex(context)) { + Log.i(TAG, "Skipping: already indexed."); // No need to update. return; } @@ -74,8 +98,6 @@ default void updateIndex(Context context, boolean force) { // Prevent scheduling multiple jobs setIndexState(context); - final ComponentName jobComponent = new ComponentName(context.getPackageName(), - DeviceIndexUpdateJobService.class.getName()); final int jobId = context.getResources().getInteger(R.integer.device_index_update); // Schedule a job so that we know it'll be able to complete, but try to run as // soon as possible. diff --git a/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java b/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java index ebba3f310ce..b49ef1d0683 100644 --- a/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java +++ b/tests/robotests/src/com/android/settings/search/DeviceIndexFeatureProviderTest.java @@ -15,7 +15,6 @@ package com.android.settings.search; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -23,27 +22,42 @@ import android.app.Activity; import android.app.job.JobScheduler; +import android.os.Binder; import android.provider.Settings; import com.android.settings.testutils.FakeFeatureFactory; 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.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; +import org.robolectric.shadows.ShadowBinder; @RunWith(SettingsRobolectricTestRunner.class) public class DeviceIndexFeatureProviderTest { + @Mock + private JobScheduler mJobScheduler; private DeviceIndexFeatureProvider mProvider; private Activity mActivity; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowBinder.reset(); FakeFeatureFactory.setupForTest(); mActivity = spy(Robolectric.buildActivity(Activity.class).create().visible().get()); mProvider = spy(new DeviceIndexFeatureProviderImpl()); + when(mActivity.getSystemService(JobScheduler.class)).thenReturn(mJobScheduler); + } + + @After + public void tearDown() { + ShadowBinder.reset(); } @Test @@ -51,7 +65,7 @@ public void updateIndex_disabled_shouldDoNothing() { when(mProvider.isIndexingEnabled()).thenReturn(false); mProvider.updateIndex(mActivity, false); - verify(mProvider, never()).index(any(), any(), any(), any(), any()); + verify(mJobScheduler, never()).schedule(any()); } @Test @@ -62,19 +76,17 @@ public void updateIndex_enabled_unprovisioned_shouldDoNothing() { mProvider.updateIndex(mActivity, false); - verify(mProvider, never()).index(any(), any(), any(), any(), any()); + verify(mJobScheduler, never()).schedule(any()); } @Test public void updateIndex_enabled_provisioned_shouldIndex() { Settings.Global.putInt(mActivity.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1); - JobScheduler jobScheduler = mock(JobScheduler.class); when(mProvider.isIndexingEnabled()).thenReturn(true); - when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); mProvider.updateIndex(mActivity, false); - verify(jobScheduler).schedule(any()); + verify(mJobScheduler).schedule(any()); } @Test @@ -87,12 +99,22 @@ public void updateIndex_enabled_provisioned_newBuild_shouldIndex() { Settings.Global.putString(mActivity.getContentResolver(), DeviceIndexFeatureProvider.LANGUAGE.toString(), DeviceIndexFeatureProvider.INDEX_LANGUAGE); - JobScheduler jobScheduler = mock(JobScheduler.class); when(mProvider.isIndexingEnabled()).thenReturn(true); - when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); mProvider.updateIndex(mActivity, false); - verify(jobScheduler).schedule(any()); + verify(mJobScheduler).schedule(any()); + } + + @Test + public void updateIndex_enabled_provisioned_differentUid_shouldNotIndex() { + Settings.Global.putInt(mActivity.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + when(mProvider.isIndexingEnabled()).thenReturn(true); + + ShadowBinder.setCallingUid(Binder.getCallingUid() + 2000); + + mProvider.updateIndex(mActivity, false); + verify(mJobScheduler, never()).schedule(any()); } @Test @@ -102,12 +124,11 @@ public void updateIndex_enabled_provisioned_newIndex_shouldIndex() { DeviceIndexFeatureProvider.setIndexState(mActivity); Settings.Global.putString(mActivity.getContentResolver(), DeviceIndexFeatureProvider.INDEX_LANGUAGE, "new language"); - JobScheduler jobScheduler = mock(JobScheduler.class); + when(mProvider.isIndexingEnabled()).thenReturn(true); - when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); mProvider.updateIndex(mActivity, false); - verify(jobScheduler).schedule(any()); + verify(mJobScheduler).schedule(any()); } @Test @@ -120,11 +141,8 @@ public void updateIndex_enabled_provisioned_sameBuild_sameLang_shouldNotIndex() // Same build and same language DeviceIndexFeatureProvider.setIndexState(mActivity); - final JobScheduler jobScheduler = mock(JobScheduler.class); - when(mActivity.getSystemService(JobScheduler.class)).thenReturn(jobScheduler); - mProvider.updateIndex(mActivity, false); - verify(jobScheduler, never()).schedule(any()); + verify(mJobScheduler, never()).schedule(any()); } } From 1582ec3db7b2e827a134aaa148f079de36696c9a Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Thu, 28 Jun 2018 14:08:42 -0700 Subject: [PATCH 19/28] Unrestrict app if it been set as device admin app Bug: 110337989 Test: RunSettingsRoboTests Change-Id: I8229bee97d970f8b16351193cfd99ca05ff041bf Merged-In: I8229bee97d970f8b16351193cfd99ca05ff041bf (cherry picked from commit f87897b84cf09da3d25ea0222fce876fdf62f302) --- src/com/android/settings/DeviceAdminAdd.java | 13 +++++++ .../android/settings/DeviceAdminAddTest.java | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/com/android/settings/DeviceAdminAdd.java b/src/com/android/settings/DeviceAdminAdd.java index 0ad882df9a2..72f4aa32cad 100644 --- a/src/com/android/settings/DeviceAdminAdd.java +++ b/src/com/android/settings/DeviceAdminAdd.java @@ -59,6 +59,7 @@ import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.users.UserDialogs; import com.android.settingslib.RestrictedLockUtils; @@ -425,6 +426,9 @@ void addAndFinish() { mDPM.setActiveAdmin(mDeviceAdmin.getComponent(), mRefreshing); EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_ACTIVATED_BY_USER, mDeviceAdmin.getActivityInfo().applicationInfo.uid); + + unrestrictAppIfPossible(BatteryUtils.getInstance(this)); + setResult(Activity.RESULT_OK); } catch (RuntimeException e) { // Something bad happened... could be that it was @@ -446,6 +450,15 @@ void addAndFinish() { finish(); } + void unrestrictAppIfPossible(BatteryUtils batteryUtils) { + // Unrestrict admin app if it is already been restricted + final String packageName = mDeviceAdmin.getComponent().getPackageName(); + final int uid = batteryUtils.getPackageUid(packageName); + if (batteryUtils.isForceAppStandbyEnabled(uid, packageName)) { + batteryUtils.setForceAppStandby(uid, packageName, AppOpsManager.MODE_ALLOWED); + } + } + void continueRemoveAction(CharSequence msg) { if (!mWaitingForRemoveMsg) { return; diff --git a/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java b/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java index 1a4ce89fbcd..1602f15263a 100644 --- a/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java +++ b/tests/robotests/src/com/android/settings/DeviceAdminAddTest.java @@ -18,23 +18,37 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.AppOpsManager; +import android.app.admin.DeviceAdminInfo; import android.content.Context; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.fuelgauge.BatteryUtils; 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; +import org.mockito.Answers; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; @RunWith(SettingsRobolectricTestRunner.class) public class DeviceAdminAddTest { + private static final int UID = 12345; + private static final String PACKAGE_NAME = "com.android.test.device.admin"; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private DeviceAdminInfo mDeviceAdmin; + @Mock + private BatteryUtils mBatteryUtils; private FakeFeatureFactory mFeatureFactory; private DeviceAdminAdd mDeviceAdminAdd; @@ -44,6 +58,10 @@ public void setUp() { mFeatureFactory = FakeFeatureFactory.setupForTest(); mDeviceAdminAdd = Robolectric.buildActivity(DeviceAdminAdd.class).get(); + + doReturn(UID).when(mBatteryUtils).getPackageUid(PACKAGE_NAME); + when(mDeviceAdmin.getComponent().getPackageName()).thenReturn(PACKAGE_NAME); + mDeviceAdminAdd.mDeviceAdmin = mDeviceAdmin; } @Test @@ -56,4 +74,23 @@ public void logSpecialPermissionChange() { verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class), eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_ADMIN_DENY), eq("app")); } + + @Test + public void unrestrictAppIfPossible_appRestricted_unrestrictApp() { + doReturn(true).when(mBatteryUtils).isForceAppStandbyEnabled(UID, PACKAGE_NAME); + + mDeviceAdminAdd.unrestrictAppIfPossible(mBatteryUtils); + + verify(mBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + } + + @Test + public void unrestrictAppIfPossible_appUnrestricted_doNothing() { + doReturn(false).when(mBatteryUtils).isForceAppStandbyEnabled(UID, PACKAGE_NAME); + + mDeviceAdminAdd.unrestrictAppIfPossible(mBatteryUtils); + + verify(mBatteryUtils, never()).setForceAppStandby(UID, PACKAGE_NAME, + AppOpsManager.MODE_ALLOWED); + } } From 0844fb92d8151773c58f808232182e4116708ca2 Mon Sep 17 00:00:00 2001 From: Pavel Grafov Date: Tue, 3 Jul 2018 20:31:42 +0100 Subject: [PATCH 20/28] Use primary user's LOCK_SCREEN_SHOW_NOTIFICATIONS. Only primary user can set LOCK_SCREEN_SHOW_NOTIFICATIONS, profile can only set notifications to be redacted. When the user changes notification settings for a work app, this class is invoked from the profile, meaning it attempts to read LOCK_SCREEN_SHOW_NOTIFICATIONS for the profile, which is not there. As a result the function always returs 0 for work apps. Bug: 111112011 Test: atest packages/apps/Settings/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java Change-Id: Ifb50209ea8ea8fb6639f00ca8b7cf8a4295890ad (cherry picked from commit f14de789f4cc7d145e6011e5a5562876e25c3f31) --- .../VisibilityPreferenceController.java | 6 +++-- .../VisibilityPreferenceControllerTest.java | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/notification/VisibilityPreferenceController.java b/src/com/android/settings/notification/VisibilityPreferenceController.java index dac90ef7be7..8dc802ccdd0 100644 --- a/src/com/android/settings/notification/VisibilityPreferenceController.java +++ b/src/com/android/settings/notification/VisibilityPreferenceController.java @@ -147,8 +147,10 @@ private int getGlobalVisibility() { } private boolean getLockscreenNotificationsEnabled() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0) != 0; + final UserInfo parentUser = mUm.getProfileParent(UserHandle.myUserId()); + final int primaryUserId = parentUser != null ? parentUser.id : UserHandle.myUserId(); + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, primaryUserId) != 0; } private boolean getLockscreenAllowPrivateNotifications() { diff --git a/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java index e37c852277f..fbc251206dc 100644 --- a/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/VisibilityPreferenceControllerTest.java @@ -208,6 +208,31 @@ public void testUpdateState_noLockScreenNotificationsGlobally() { .contains(String.valueOf(VISIBILITY_PRIVATE))); } + @Test + public void testUpdateState_noLockScreenNotificationsGloballyInProfile() { + final int primaryUserId = 2; + final UserInfo primaryUserInfo = new UserInfo(primaryUserId, "user 2", 0); + when(mUm.getProfileParent(anyInt())).thenReturn(primaryUserInfo); + + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, primaryUserId); + + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + mController.onResume(appRow, channel, null, null); + + RestrictedListPreference pref = mock(RestrictedListPreference.class); + mController.updateState(pref); + + ArgumentCaptor argumentCaptor = + ArgumentCaptor.forClass(CharSequence[].class); + verify(pref, times(1)).setEntryValues(argumentCaptor.capture()); + assertFalse(toStringList(argumentCaptor.getValue()) + .contains(String.valueOf(VISIBILITY_NO_OVERRIDE))); + assertFalse(toStringList(argumentCaptor.getValue()) + .contains(String.valueOf(VISIBILITY_PRIVATE))); + } + @Test public void testUpdateState_noPrivateLockScreenNotificationsGlobally() { Settings.Secure.putInt(mContext.getContentResolver(), From af4c772b3083de944c7d71a24aadd703bd1aa79f Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 31 Jul 2018 14:04:44 -0700 Subject: [PATCH 21/28] 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 (cherry picked from commit 770f4abf9de2bb7d74497cc4b5f6795023229ef2) --- .../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 @@ protected void onCreate(Bundle savedState) { 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 @@ protected void onCreate(Bundle savedState) { 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 int getMetricsCategory() { @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 void onCreate(Bundle savedInstanceState) { 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 RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup pare 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.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 @@ private SearchMenuController(@NonNull Fragment host) { @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 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.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 void init_doNotNeedSearchIcon_shouldNotAddMenu() { 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 d91071fe7d1cc611fae28e0e7d8847074c1e4505 Mon Sep 17 00:00:00 2001 From: maxwen Date: Thu, 31 Aug 2017 23:16:42 +0800 Subject: [PATCH 22/28] Add custom Stag Horns dashboard Change-Id: I0bd41e702b6ae1b56de00a7be5fcdce0f5174722 --- Android.mk | 21 ++++++++ AndroidManifest.xml | 32 +++++++++++- proguard.flags | 2 + res/drawable/ic_homepage_stag.xml | 50 +++++++++++++++++++ res/values/colors.xml | 1 + res/values/stag_strings.xml | 18 +++++++ src/com/android/settings/Settings.java | 1 + .../core/gateway/SettingsGateway.java | 4 ++ .../dashboard/DashboardFragmentRegistry.java | 4 ++ 9 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 res/drawable/ic_homepage_stag.xml create mode 100644 res/values/stag_strings.xml diff --git a/Android.mk b/Android.mk index e37e9fbff1a..81932a40f9a 100644 --- a/Android.mk +++ b/Android.mk @@ -19,6 +19,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_USE_AAPT2 := true LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES += $(call all-java-files-under, ../Horns/src) LOCAL_STATIC_ANDROID_LIBRARIES := \ android-slices-builders \ @@ -32,6 +33,7 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-support-v7-preference \ android-support-v7-recyclerview \ android-support-v14-preference \ + android-support-design LOCAL_JAVA_LIBRARIES := \ bouncycastle \ @@ -45,14 +47,33 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ jsr305 \ settings-logtags \ +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ + frameworks/support/v7/preference/res \ + frameworks/support/v14/preference/res \ + frameworks/support/v7/appcompat/res \ + frameworks/support/v7/recyclerview/res \ + frameworks/support/design/res \ + packages/apps/Horns/res + LOCAL_PROGUARD_FLAG_FILES := proguard.flags +LOCAL_AAPT_FLAGS := --auto-add-overlay \ + --extra-packages android.support.v7.preference \ + --extra-packages android.support.v14.preference \ + --extra-packages android.support.v17.preference \ + --extra-packages android.support.v7.appcompat \ + --extra-packages android.support.v7.recyclerview \ + --extra-packages android.support.design \ + --extra-packages com.stag.settings + ifneq ($(INCREMENTAL_BUILDS),) LOCAL_PROGUARD_ENABLED := disabled LOCAL_JACK_ENABLED := incremental LOCAL_JACK_FLAGS := --multi-dex native endif +LOCAL_DEX_PREOPT := false + include frameworks/opt/setupwizard/library/common-gingerbread.mk include frameworks/base/packages/SettingsLib/common.mk diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 27132165b91..06f3effaf27 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3147,7 +3147,7 @@ android:icon="@drawable/ic_homepage_system_dashboard" android:taskAffinity="com.android.settings" android:parentActivityName="Settings"> - + - + @@ -3327,6 +3327,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/proguard.flags b/proguard.flags index 43a038b78a7..e2b678dd7b2 100644 --- a/proguard.flags +++ b/proguard.flags @@ -13,6 +13,8 @@ *; } +-keep class com.stag.settings.** + # We want to keep methods in Activity that could be used in the XML attribute onClick. -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); diff --git a/res/drawable/ic_homepage_stag.xml b/res/drawable/ic_homepage_stag.xml new file mode 100644 index 00000000000..9269af6cb60 --- /dev/null +++ b/res/drawable/ic_homepage_stag.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index e5f7c276ae9..2132abe9f70 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -125,6 +125,7 @@ #757575 #26459C #1A73E8 + #00bcd4 @*android:color/material_red_A700 diff --git a/res/values/stag_strings.xml b/res/values/stag_strings.xml new file mode 100644 index 00000000000..27023d3bb96 --- /dev/null +++ b/res/values/stag_strings.xml @@ -0,0 +1,18 @@ + + + + + Horns + Where the Stag grows + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 0ad964b65b3..3593b9333f5 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -164,5 +164,6 @@ public static class AppAndNotificationDashboardActivity extends SettingsActivity public static class StorageDashboardActivity extends SettingsActivity {} public static class AccountDashboardActivity extends SettingsActivity {} public static class SystemDashboardActivity extends SettingsActivity {} + public static class StagSettingsActivity extends SettingsActivity {} } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index a8cb61cd848..0e1b655a9f2 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -137,6 +137,8 @@ import com.android.settings.wifi.calling.WifiCallingSettings; import com.android.settings.wifi.p2p.WifiP2pSettings; +import com.stagf.settings.StagSettings; + public class SettingsGateway { /** @@ -261,6 +263,7 @@ public class SettingsGateway { DirectoryAccessDetails.class.getName(), ToggleBackupSettingFragment.class.getName(), PreviouslyConnectedDeviceDashboardFragment.class.getName(), + StagSettings.class.getName() }; public static final String[] SETTINGS_FOR_RESTRICTED = { @@ -299,5 +302,6 @@ public class SettingsGateway { Settings.DeviceInfoSettingsActivity.class.getName(), Settings.EnterprisePrivacySettingsActivity.class.getName(), Settings.MyDeviceInfoActivity.class.getName(), + Settings.StagSettingsActivity.class.getName(), }; } diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java index cd478fc43ad..57eed14c220 100644 --- a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java +++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java @@ -40,6 +40,8 @@ import com.android.settings.system.SystemDashboardFragment; import com.android.settingslib.drawer.CategoryKey; +import com.stag.settings.StagSettings; + import java.util.Map; /** @@ -101,6 +103,8 @@ public class DashboardFragmentRegistry { CategoryKey.CATEGORY_GESTURES); PARENT_TO_CATEGORY_KEY_MAP.put(NightDisplaySettings.class.getName(), CategoryKey.CATEGORY_NIGHT_DISPLAY); + PARENT_TO_CATEGORY_KEY_MAP.put(StagSettings.class.getName(), + CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT); CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size()); From 03da95c4df9081c10ffeb2b4feeaac13e9a787fd Mon Sep 17 00:00:00 2001 From: vjspranav Date: Sat, 27 Oct 2018 22:05:36 +0200 Subject: [PATCH 23/28] Move GesturesStagSettings (1/2) --- res/xml/system_dashboard_fragment.xml | 10 +--------- .../android/settings/core/gateway/SettingsGateway.java | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml index 1ca86ab1ebf..9d446c8c496 100644 --- a/res/xml/system_dashboard_fragment.xml +++ b/res/xml/system_dashboard_fragment.xml @@ -21,14 +21,6 @@ android:title="@string/header_category_system" settings:initialExpandedChildrenCount="4"> - - - \ No newline at end of file + diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 0e1b655a9f2..7e9c6ccf423 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -137,7 +137,7 @@ import com.android.settings.wifi.calling.WifiCallingSettings; import com.android.settings.wifi.p2p.WifiP2pSettings; -import com.stagf.settings.StagSettings; +import com.stag.settings.StagSettings; public class SettingsGateway { From 5c70f8117a9642f7bc70b4b56c2971109436532f Mon Sep 17 00:00:00 2001 From: Rakesh Kommula <35953548+Rakhi15@users.noreply.github.com> Date: Fri, 2 Nov 2018 22:21:35 +0530 Subject: [PATCH 24/28] Create README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000000..c0d1ad95706 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# android_packages_apps_Settings From d081ff53411ecd3fc77a0a0a3b25afc1113641f6 Mon Sep 17 00:00:00 2001 From: Rakesh Kommula <35953548+Rakhi15@users.noreply.github.com> Date: Fri, 2 Nov 2018 23:49:41 +0530 Subject: [PATCH 25/28] added logo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c0d1ad95706..46d52dd4caa 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ # android_packages_apps_Settings +added stagOS logo in Settings DMS From a1d248ecd02b8b080d7a9c06994c74c7c0c4e656 Mon Sep 17 00:00:00 2001 From: Rakesh Kommula <35953548+Rakhi15@users.noreply.github.com> Date: Fri, 2 Nov 2018 23:51:56 +0530 Subject: [PATCH 26/28] added stagos logo --- res/drawable/stagoslogo.png | Bin 0 -> 58688 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/drawable/stagoslogo.png diff --git a/res/drawable/stagoslogo.png b/res/drawable/stagoslogo.png new file mode 100644 index 0000000000000000000000000000000000000000..2b69436fa48a6e5dc60eb1e224663d6fa948b87c GIT binary patch literal 58688 zcmdpd^;gty)a}p+N_RI%hXY8XG*Z&t-Q5g`NVjxKGjw;CfPi#^gv8L@%scYE@4f%S zonL0HS!+H|?X%B5`#GO*Wku;XXe4L=0N{ivwWxPOS>!FNk2UIBh*yX`Xks#Y%gV_=!JK< z7$)%&Z@0t49ZlZt$VP=vT75FHTfp}fmy9p^f(N&+YGf2>e7_QK?E`K0WS&kVw)k`N zfZ<`d~p4XB#+jCWaV@AK%~eWRHc7t)kS6h=Prc4a@~wpv2+A z6$KFVOt`Xuxg){gsiu@~D2{L6Dwaht`l)QSQe!IO_j?U;*+hB4YY~+I(1Ic&#B!A< zmnujB5!cowDqJRSa_7+c6)foRN~@4L;Y%7@CmONn_&T8EXbWmC5_rmR64YRu zT&xBs{5eNl-qpHryZ$X6l{`i9TVGb>d>=;=YNH-UoOF^*S4wU(zuNYrNzB4YKp3oI_-|~~rE&pl|CzR}XoY3QN+xECW?F~ok>~>{z4;pEJQIL({3mp) zd_9S$9Eha=B+=)F?HBx=Djdb&XQiOh3~;X@$ldgrFqt6f`mNilSpRLJ{HFw4na`U3 z2KS^T`ZoDaGd5zl202#zB6Ws}S4h$^Ocdmw+U`nNp%11KBN!ef|5m`=IZ91K12I>d z9Yc{~U(mi!lx&OS(EhBKrn+ZMMI*xK5EX=~<`sUz*#JPqC`WGpdSr`Rlg~2^W+xGL z8n&iISwDb_w2b(Q;gS09d06XDFpOTogKb0*6urzXhgT;mR=KG;jJZe1ayWK~5K^~s z-jtR~OzO)^VTgl=N$B3fizUkI`~lne(4-SZcx^v&<)d??^Itx_?~V-_#-O3eqX;?) z2n!4IVN!q7iGM!+;H5c9Nx7)ZS^fJ}ktEstRllr3mu=N1vKTzPs~AwY^ewPUrSySo zwu9bkiIW=l!6(XR*HQ|rrTr4E`@a7|1+R3_auUSQX&AKctUmrd! z@6-5Vc)0z0ChmXF3`_#=3e}mL!Y^d|phHimfhP z&d3$(BgQ}r3n>A>19`o>0Hp=5)A`9t^$8NnNolIV0JQ{B(lD!|N(mc_(sz8Cg$~|| zt12_1-|klp1=5NV$a#%XeB$@4gI(mhgfB|wCjgbW@e;cyaj=C!OkZM?N?X6kbj~+k z`nl8PXglc@W8KnO3&C(22@S!Lp2-hfprQwzW4ziAg%DJ#Y`c`UuT!^b=F?a5!D!Y}U7y3) zf6gcPQPIy#igRjxw?76(EFKa%gN(to@P$v@2&1)u4G2MbJ!1HQM1W;{1kc$eu~i`| z{lLz`?%M<+#dUoHk!DUjinf*lpWbn&cg;Krr-V|yLDgsPUxneilGSX^po8TZ`e*7u ztxWECun4rENlqnmVyVjH^xHt9Mb;bgA&0>vEArJpU^`T+x_9FW${7Zjt?tO`{uxDW z6B%OuoTWsf#AxM^>ldh|Ix3__UPki!>sZtf&PQ?y%UCiz0cOTB6EllB1Z(S{Xj23& zf4XW-36m^JnR#XPL=>|VIU^tEUctNq=Etlz0}nA;Eyb)C(^C*5cd9>;MDyXK(rQ#X zJi3J!v_WE!Cv*m=uq0e}uVn@Kc8DoqG+FHhimKEqr|kADJL(r0wO9W~JA^(QIPWdj zcCw(E8BLbTvQ%twSkgM#vPQc5-=<2+q!VxCA(1F52r9Gn5-ZoUUVecLG?tXY#wRlY zi)~8dul)GXwiO6UA*XQHIADka(zl(Vvo^`L(HQM?O-?gYZ+9tTzh`E{Zn`Kg=Jd?P z1XhwS_gCWSi#Cw5+NnavYSz(;isiGK;^yV8;wBqY9CyAMUr|GSJa;c*gd$Kv{5h?-G5mxPryRwUpg!rgVQKTiMX zmON~~zsx>g8@}`vj7#s^o5}Iehb-RwDi!kV4O6GB{elS^KgnUt#QtpowwVJYYNULv z)KabAEpx;_+|NK^ByxdwpqV9Mc>j<$3LiM3Blr$GCcyjgRAHX-{Udt5q)VGa=syx7 zc!>^--yX98C&%brPYj@SW zb-s*2m%fHhb9HZJW=*!BsSWunCwW~iHe?J2G*>aV&FtPtN15U=v>X@gP`2>2)t2-I z*>qVkdCZoGw^e0x-?K1=u^tvw;9_Hw*QWlVg5{OmBk)r;^TNd@!U4L)HVicmQ9S`H zDgr-@N|+_^14x_van2U@5$Z-KDBhcGME!W*M2T5$ygYeaqvNg|pnf^mbm5_YDrM~4 z^&!=${}q4sJKci(;LjnJv%PTjjzV}{cKx{>>mn{QO-iUEq*-kou&DZo@`8h-0`NYF zmC1Xn=PDz1jK|uVYb(`^dSnhQkT=;5Ap=sQa^r{HtrMnxYizGrF5vO}8}9FJ6!G=; z_W)v0+PFD7N*1`NizotQZmx1R7F#t+UOk{5cKyEY#p8YQ#SlHJfqd&t06+_YN zP~Ro}20#)>h8-p0@--f$`p!&i{r=96Wwze$y}m&Y{aBp1CZ|3&#qZ$dvs_6j()HHy zj{zT1)a|1boLB+R&gsk{Dux>De>INLMpAiCZ`FU7cR=T1gD9Svhrd{SNU?;rv_%H?r6opwJt~8sVx79kOB1)svo$N`i zBJiSa>L*mV;VpFP<|g@u?Fz}?c_`J)d8+jT`LI+C`EE&Zu*t@rCu7XpW|PBip0qvK zshj-EJ0+5oWdSEUNM+CKQ>i;&^>Y$4y{SvLzXfCX^H%9Rvy8h1>oEW^-q@-!2IzhEp6|15%&e!e;rXc8t%5DfEt#w<+Oys&s*d%FKv#3?*&|H<)+>m)F^Ayxt$tGaJsJG+ zfn}#T08hY#nqysO<-DBAC`hh_{niZBK8}a*bHx$Z$|r`n6q+i3(v}bNHy^3 z%D0=ay`q32HdBJB4N>?+_=pvZDbo z?UkGg5~$I{gbL_|93#q-_$UO=D#{*;{69*3(02j2$!O!uvYhC5oW)iVXue^(LGedp z37kH25KdjH@x&9Rmp@m7{I?!1UgmA5D?wNZ2 zgzOey1^ZKfyLDwn``N85reRW+2TuyJvUEH7EY!zr_FGqHdy|crH|L%XFoD%+`Cc}i zHE6kcIifdYmg~%E&$Bq^bSH&-`@AwD3l{rEFOh)30=C)F_?ohI*^_SFhCmF*61QVv zgNnB_Rrne4lyz#R4j1_Ou5DV;>+i+BivnSYVrbeGxb9f5Cpb zlvwwDPTp=g)G5pFaN2`rd0pmfUX49whb<6f!&1zQ?F+wzhDghOxs6>j?$Q%|YN|iqi>`^nimg2)oB#lo_SlE-)(&}TBXgB^&yXlW< z^deo9Q^^Dw3S&FU&2td1s@8juQabNVy2$>zc?Ud1Hn}wW0wg~#eC;F2y7;D~M^{js zJAD3IlP_52Lde(S+hN<$_fkeh!r`O5I9L*Bef=*J!s4S!;1bIAWcRmycRd zBEja8_v1E}A@;41`(=0j?z@Z9>qKb4!v+WF;;@Qb`L_hh-dJ;|&+0*>B8SXD;_;87 z3OijK-zz4nY5@j45}(UFRS>B5r@cN7D=dNut#^}RVWhw7Ge1qr?rXbPWC?B;SJ`T0 zX`$x5(+Z-&Qd&q)KRx{*)_tJsb+(4e;fn$AX_u!S%=Jzo=Z34=)ej%l z-7z`c{z>Y-NCH?y;5-|sHU2s06HV9O8NIa4NG^C+kjIHej#n15=5p8{TSb}R-x$`f z<1_y8rt2NW+CXs3MHG==INy<`{JM0yTz5wF>6e?z6fIKlVO0HwKNv=$K0tMMP&+#xbcm%X zO9wKsr|g2DjA-9G?wn5_DFW(!yH2{aTR)dT3c#vVEPyki@I6iHAzyf_lJPxhzgeB~ zWDk>T=V(4j`YyTP1 zc-&P^w~32-O(4mwo;0rp!I=;}KNdcX!bB@&jx1{U`<^D@HYfgRu*2)vxruTsgxQbx z$M5^_n4$!V#tqFxz1rF-#CyG4h_s`qy20Hpf&domEZ7I>nPHP*=^&jgtMoCwLm>6< zc|9?Mr*4TSf8atdYJB+;PCc)Q|q_`}y`1^P{9TQ|pRDt^6~ z(y#ejwxmD8R_XrVmhV1&6?q)sOZK;Rq~a-Yf|G6ReOL1E+6Q!%NE&e{!S6g>z$NJL zJ`akBEnCm`ZOryssF1gTVld23D?_xlV|nk5+H62dGigY^g$1b&{3wlIT03 z)}`REn%KXRTT<;-Vpte-ChAYY;}Ja{c|hFA3%*Q^XKbsQ#DOv{@ZW%nzBxJIF)j%C ztaK-r&2>QK)fv(f$gvjV zgkWPcsfp+`R%$Zxs{xy>EN^R@ojKSe85BP;)e1R(iQOG|;^d5bO-s`djt-YQTjG%<&@$C1Otv{C#!p;AlUwyP`%bwVJ6iP;a%>EGDkoxiq%!+Eb{*tY34~l>F2mnFW`- z`U(R;#F9`2rak`2Vc@gd+FtrMD6pzfnD5pA2}}G}^UiD>dG$#aKIvR(_IqtJb!n4- z)}YF;XZJR#@Zcq2~EV#iYF)UUe3#0G*T=FTtNrC%rGP$BAFb7JSmjou0pRUD#0PtRfOoR=XEh~-cR|cJ0&WiCLaV*IxClWZVu+jxU{B}j z{3C&eG*Tpqa=>PZj1IiaaumDwZH-f0zhf3Jt8bq;Mq7}?Oe@e` z9KX93Qv>rb>S+A@;Y7De09q7MmeP(d+4YrZ8ZxZRBQFOiqaj^6ESERXzP@fx7|AhM z;y73v4~v9FVgABY3=EIu_Tb>I=d}@qDvd>Pt}Qv2dYfgZGwwiUy(_om{t<(Ar#Yk) zbEK-HUkf90Wse#P9j&-1pvJxm@bP2sZ*q>m>W=>0dLX0!C#ClmagRIpU(Dc zAs7?P|G@Y9Kk$7o7TAe&^s9Fk?_KAhma-XCcfa7%(RsB=%@$w1PL!#Cd8<7Xo%Xgi zm&f$MYjTHjso7cAR$*Cx?z?oA4|S8k&Cf@z#pACJ(`aYWXHjM!W`Pg$&xpzw2ZzLG zo+;a0e9mh!=P7hprn9dkHckvT?lQ&9W?5QIe!Z79RHN z^QBh?5_IPo&L4+__z!;0qey^F*>VWdeJ-pLFjWC2Fsp$0aV^iCuY*+nv5d!*mWIX# z^Mxme7E1;GO!0~8wT>uEG!DmGR|IiKeCE?Byi)=qsL#Q(YKOeU5U)kV4CaZb8_ zZiXG7Xe+Ysk|p_)SpIygE$0{dk#`g4b=46#i#Iq=eF(_PjRuI1yTZ3CKfYX~he`YP z+u;kP+b6B6TAdlynflREby3Ngyd&i9VNO#*_vu)t~jkM%Nthz;C| z5UZ5fFVi6)0Z^0&;;h^+Id~X_Zz8!q#vXycqKmvr!;D`oWG8HoMh` ze-|J|HTr!us_Glwd#}R!Fn6^8@};JubyWq*{j_ z*4nML_G0t#!c%XP#yDieKMMP;uiZ{Ots~iD71dazFB`-A&lRRWfbT`cTR2vqo@=iX zp)GALDw>0`5)x{ZFWYFUfK}S;c|8-JV)5ShO}d;5Dw)FGF8spH@0KBQ9(^-=%^@{| z@1ksE5%(nXYjKKTelU1v?8^va_Am6L?a)kLlT8ebty_Sq<9L|tQ62dx${xTfm;-1? zub$V78WoYK^5gicdg1yzv{xH8ez8 zG1JIzi7$M2A?(g>eQMrSbwyz;0Y8hwZ{i;!0{!Gr!pTukhiNr`(vvR57zoeAfr-Q} z)OI?9u;f9yDr9rI?>Uivuc0VJRz)e>#}|+M21E@{B%UU^+OeNMVKGq_Ci^!eDzu^fQa1UdDqC0H($m99A+k7S+CR5U8k_KH0MS!$^rz_?&?()=`8wlmOl~=GyjkV;d8Xpt0$PtwA-t73k1_yR`q`K{R@V^A zgjvvH=nI|3%`PqRX^!lpgP9IwFX&+Tg&qEQy#b3l8z?+K>B+5w8`3ZC|o8K z2c5jZ*xuNpPZuwchE_0N-?;>wW=Q07xc_K%rZ}O8N606SDD^cw`eP74Ij98Nu>Ter zL~t$q^ugfjy09%jrNL9zdSCIwf%!~AR${dGNd@M_{;yPBSCv-ZVNu1L^IhXJuuSbA zaNb?rEL}=v7(6G_P4PqLL(l!FArsq?a$YDbHL|@3bcZHA;FInJp?_SlK#}l727B#p zD5Da^#qXJTjy0h7j2X79n|hSO+ts4t5SBlV%|;RcB;@Of@OpQ_NDmE>TZsU>_3W>{ z=46VL*v2MXaSSFBe$;1}>jci(sVSOSZF<0tiU|)thxTJOU481f!jr1e{YNcb4{MA2 zu~>hH9mmj+vffix9et4`W+CP|nxVn1X&09{Px)L}p|17f7qupVkxw2^>ndWGercz) z-<^#NfK`_+9^cc%bG)IPDYQ4rxPXvGKIa9}n*6r<(y5ppU>0CI0j;KU?tyW_>RrvB zdG${*G-(Tfge|e;SHjz`)ShNfW0&->SOO}jT6LD=_MD>6AmbQtMnB;D^Ke+1Biya| zl8Q3-`I|&NH^TYIu-}A8c34mpHaKz*_z+Q6l=$rTF7C$SF3#w~0&H?4^o6^4!8V;; z?4Z8(^Zwk_t=1d?>qSwoi~NBDbw;wog{933O~Po2vG38yDay0;^+xKh6U!w6kA?P0 z8BO1pcee)luxK2%hM$1s@At460f-5KvMee*e@h=1D(^ymJm~n0{906Hf#_7j2g+7H zp7y6f4>up$-mO_=(w$F$vb_)A(9+Htt3(#1qiO^42&Czm%d(rDt_Kn&lHEZCuZt4V`*{Kj>t~5Zq5#jHi0R;tG8^2cgE7Qni`n$opC;>!)ayl z`d*5r`>YAx{f*SLi?uIzn;s133IOCOGw@)zmS)p3z$NN`cj~%OGibI_XrP#%G3A+h z^bWL-6PKCW!16v@^bGZsZdZ8E*9V<|W_qtE1n19gnDOST9GD##>(%E$YA!4dg;dRY zdcqUJ!Y~+LBu>+ue0G8yKxW@3_fR=46p{9!Ylk0D48wY-jVhm|qS?=3BEPkjG=!F41dK$nzI9c}FYFk_-u zo(E|`x5ydWMuw3S;R`v93ab)4=j8Y`8+#<-I%VjD86(w>5C3X?9>Ww}So@e#=GX4k zN0@6Oah=fzHy1jc$uJNPhjeAY90oJNEwU85mgk6NH= zIR!)B2`POdD^fD}wQ|0B{gXi59VHfR9J8p;)^8*X^>sfh)TnQ_O>ldV{TJ%Wb9c*- z@S3O|J_X_1s@t3QjQae#+gZWng#T}Pb+BvSruE0$hNGZgZZc0vZ;L#B8S-6fYB`(x zb21*B&nL4xD3Oj;HlAKxiK3}XBD_h!abFd1eoop&F&wWdU%=yTV(dSn1tTZ4_H)OJ zA5qC9*J6cKjilEo{T-5nVdBII9>RO&#nL?_&wIVt;

{(?7s9J)b7?J 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 literal 0 HcmV?d00001 From 2412e8134e53966da4481bdc19bba28a17dd3499 Mon Sep 17 00:00:00 2001 From: Rakesh Kommula <35953548+Rakhi15@users.noreply.github.com> Date: Fri, 2 Nov 2018 23:55:07 +0530 Subject: [PATCH 27/28] Delete dialog_firmeware_version.xml --- res/layout/dialog_firmware_version.xml | 91 -------------------------- 1 file changed, 91 deletions(-) delete mode 100644 res/layout/dialog_firmware_version.xml diff --git a/res/layout/dialog_firmware_version.xml b/res/layout/dialog_firmware_version.xml deleted file mode 100644 index 874d7af0063..00000000000 --- a/res/layout/dialog_firmware_version.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - From 8deddfd9ac0437a52a1879411652a9958a58d6ae Mon Sep 17 00:00:00 2001 From: Rakesh Kommula <35953548+Rakhi15@users.noreply.github.com> Date: Fri, 2 Nov 2018 23:57:04 +0530 Subject: [PATCH 28/28] Added firmware_version --- res/layout/dialog_firmware_version.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 res/layout/dialog_firmware_version.xml diff --git a/res/layout/dialog_firmware_version.xml b/res/layout/dialog_firmware_version.xml new file mode 100644 index 00000000000..df2200fe2e4 --- /dev/null +++ b/res/layout/dialog_firmware_version.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file