diff --git a/res/values/strings.xml b/res/values/strings.xml index f0a57abf8de..8868b4b5eab 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10042,17 +10042,24 @@ ANGLE enabled application: %1$s - - Use Game Update Package - - No selected app - - %1$s - Game Update Packages Preferences Modify Game Update Packages settings + + Select Graphics Driver + + Default + + Game Update Packages + + Native Graphics Driver + + + @string/gup_app_preference_default + @string/gup_app_preference_gup + @string/gup_app_preference_native + diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 2eedca55729..a5e26f654f8 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -195,7 +195,8 @@ android:key="gup_dashboard" android:title="@string/gup_dashboard_title" android:summary="@string/gup_dashboard_summary" - android:fragment="com.android.settings.development.gup.GupDashboard" /> + android:fragment="com.android.settings.development.gup.GupDashboard" + settings:searchable="false" /> @@ -430,11 +431,6 @@ android:summary="%s" android:title="@string/simulate_color_space" /> - - + xmlns:settings="http://schemas.android.com/apk/res-auto" + android:key="gup_settings" + android:title="@string/gup_dashboard_title"> + + + + + diff --git a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java index a67aac4d3a5..564f2c35c47 100644 --- a/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java +++ b/src/com/android/settings/development/DevelopmentOptionsActivityRequestCodes.java @@ -31,6 +31,4 @@ public interface DevelopmentOptionsActivityRequestCodes { int REQUEST_CODE_ANGLE_DRIVER_PKGS = 4; int REQUEST_CODE_ANGLE_DRIVER_VALUES = 5; - - int REQUEST_CODE_GUP_DEV_OPT_IN_APPS = 6; } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 5990320321c..725a1953429 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -423,7 +423,6 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new SelectDebugAppPreferenceController(context, fragment)); controllers.add(new WaitForDebuggerPreferenceController(context)); controllers.add(new EnableGpuDebugLayersPreferenceController(context)); - controllers.add(new GameUpdatePackageDevOptInPreferenceController(context, fragment)); controllers.add(new VerifyAppsOverUsbPreferenceController(context)); controllers.add(new LogdSizePreferenceController(context)); controllers.add(new LogPersistPreferenceController(context, fragment, lifecycle)); diff --git a/src/com/android/settings/development/GameUpdatePackageDevOptInPreferenceController.java b/src/com/android/settings/development/GameUpdatePackageDevOptInPreferenceController.java deleted file mode 100644 index 2d295055ea0..00000000000 --- a/src/com/android/settings/development/GameUpdatePackageDevOptInPreferenceController.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 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.development; - -import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes - .REQUEST_CODE_GUP_DEV_OPT_IN_APPS; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.provider.Settings; -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; - -import com.android.settings.R; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.development.DeveloperOptionsPreferenceController; - -// TODO(b/119221883): Need to override isAvailable() to return false when updatable graphics driver is not supported. -public class GameUpdatePackageDevOptInPreferenceController - extends DeveloperOptionsPreferenceController - implements PreferenceControllerMixin, OnActivityResultListener { - - private static final String GUP_DEV_OPT_IN_APP_KEY = "gup_dev_opt_in_app"; - - private final DevelopmentSettingsDashboardFragment mFragment; - private final PackageManager mPackageManager; - - public GameUpdatePackageDevOptInPreferenceController(Context context, - DevelopmentSettingsDashboardFragment fragment) { - super(context); - mFragment = fragment; - mPackageManager = mContext.getPackageManager(); - } - - @Override - public String getPreferenceKey() { - return GUP_DEV_OPT_IN_APP_KEY; - } - - @Override - public boolean handlePreferenceTreeClick(Preference preference) { - if (GUP_DEV_OPT_IN_APP_KEY.equals(preference.getKey())) { - // pass it on to settings - final Intent intent = getActivityStartIntent(); - mFragment.startActivityForResult(intent, REQUEST_CODE_GUP_DEV_OPT_IN_APPS); - return true; - } - return false; - } - - @Override - public void updateState(Preference preference) { - updatePreferenceSummary(); - } - - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode != REQUEST_CODE_GUP_DEV_OPT_IN_APPS - || resultCode != Activity.RESULT_OK) { - return false; - } - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.GUP_DEV_OPT_IN_APPS, data.getAction()); - updatePreferenceSummary(); - return true; - } - - @Override - protected void onDeveloperOptionsSwitchDisabled() { - super.onDeveloperOptionsSwitchDisabled(); - mPreference.setSummary(mContext.getResources().getString( - R.string.gup_dev_opt_in_app_not_set)); - } - - @VisibleForTesting - Intent getActivityStartIntent() { - Intent intent = new Intent(mContext, AppPicker.class); - intent.putExtra(AppPicker.EXTRA_NON_SYSTEM, true /* value */); - return intent; - } - - private void updatePreferenceSummary() { - final String optInApp = Settings.Global.getString( - mContext.getContentResolver(), Settings.Global.GUP_DEV_OPT_IN_APPS); - if (optInApp != null && !optInApp.isEmpty()) { - mPreference.setSummary(mContext.getResources().getString( - R.string.gup_dev_opt_in_app_set, getAppLabel(optInApp))); - } else { - mPreference.setSummary(mContext.getResources().getString( - R.string.gup_dev_opt_in_app_not_set)); - } - } - - private String getAppLabel(String applicationPackageName) { - try { - final ApplicationInfo ai = mPackageManager.getApplicationInfo(applicationPackageName, - PackageManager.GET_DISABLED_COMPONENTS); - final CharSequence lab = mPackageManager.getApplicationLabel(ai); - return lab != null ? lab.toString() : applicationPackageName; - } catch (PackageManager.NameNotFoundException e) { - return applicationPackageName; - } - } -} diff --git a/src/com/android/settings/development/gup/GupDashboard.java b/src/com/android/settings/development/gup/GupDashboard.java index 674a0a90d0e..31f01dd08eb 100644 --- a/src/com/android/settings/development/gup/GupDashboard.java +++ b/src/com/android/settings/development/gup/GupDashboard.java @@ -17,14 +17,22 @@ package com.android.settings.development.gup; import android.content.Context; +import android.provider.SearchIndexableResource; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.development.DevelopmentSettingsEnabler; +import com.android.settingslib.search.SearchIndexable; +import java.util.ArrayList; import java.util.List; +@SearchIndexable public class GupDashboard extends DashboardFragment { private static final String TAG = "GupDashboard"; @@ -47,4 +55,22 @@ public class GupDashboard extends DashboardFragment { public int getHelpResource() { return 0; } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final List result = new ArrayList<>(); + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.gup_settings; + result.add(sir); + return result; + } + + @Override + protected boolean isPageSearchEnabled(Context context) { + return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context); + } + }; } diff --git a/src/com/android/settings/development/gup/GupPreferenceController.java b/src/com/android/settings/development/gup/GupPreferenceController.java new file mode 100644 index 00000000000..762314474ef --- /dev/null +++ b/src/com/android/settings/development/gup/GupPreferenceController.java @@ -0,0 +1,206 @@ +/* + * Copyright 2019 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.development.gup; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.provider.Settings; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.development.DevelopmentSettingsEnabler; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GupPreferenceController + extends BasePreferenceController implements Preference.OnPreferenceChangeListener { + private final CharSequence[] mEntryList; + private final String mPreferenceTitle; + private final String mPreferenceDefault; + private final String mPreferenceGup; + private final String mPreferenceNative; + + private final List mAppInfos; + private final Set mDevOptInApps; + private final Set mDevOptOutApps; + + public GupPreferenceController(Context context, String key) { + super(context, key); + + final Resources resources = context.getResources(); + mEntryList = resources.getStringArray(R.array.gup_app_preference_values); + mPreferenceTitle = resources.getString(R.string.gup_app_preference_title); + mPreferenceDefault = resources.getString(R.string.gup_app_preference_default); + mPreferenceGup = resources.getString(R.string.gup_app_preference_gup); + mPreferenceNative = resources.getString(R.string.gup_app_preference_native); + + // TODO: Move this task to background if there's potential ANR/Jank. + // Update the UI when all the app infos are ready. + mAppInfos = getAppInfos(context); + + final ContentResolver contentResolver = context.getContentResolver(); + mDevOptInApps = + getGlobalSettingsString(contentResolver, Settings.Global.GUP_DEV_OPT_IN_APPS); + mDevOptOutApps = + getGlobalSettingsString(contentResolver, Settings.Global.GUP_DEV_OPT_OUT_APPS); + } + + @Override + public int getAvailabilityStatus() { + return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext) + ? AVAILABLE + : DISABLED_DEPENDENT_SETTING; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final PreferenceGroup preferenceGroup = + (PreferenceGroup) screen.findPreference(getPreferenceKey()); + if (preferenceGroup == null) { + return; + } + + for (AppInfo appInfo : mAppInfos) { + preferenceGroup.addPreference( + createListPreference(appInfo.info.packageName, appInfo.label)); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final ListPreference listPref = (ListPreference) preference; + final String value = newValue.toString(); + final String packageName = preference.getKey(); + + // When user choose a new preference, update both Sets for + // opt-in and opt-out apps. Then set the new summary text. + if (value.equals(mPreferenceNative)) { + mDevOptInApps.remove(packageName); + mDevOptOutApps.add(packageName); + listPref.setSummary(mPreferenceNative); + } else if (value.equals(mPreferenceGup)) { + mDevOptInApps.add(packageName); + mDevOptOutApps.remove(packageName); + listPref.setSummary(mPreferenceGup); + } else { + mDevOptInApps.remove(packageName); + mDevOptOutApps.remove(packageName); + listPref.setSummary(mPreferenceDefault); + } + + // Push the updated Sets for opt-in and opt-out apps to + // corresponding Settings.Global.GUP_DEV_OPT_(IN|OUT)_APPS + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.GUP_DEV_OPT_IN_APPS, String.join(",", mDevOptInApps)); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.GUP_DEV_OPT_OUT_APPS, String.join(",", mDevOptOutApps)); + + return true; + } + + // AppInfo class to achieve loading the application label only once + class AppInfo { + AppInfo(PackageManager packageManager, ApplicationInfo applicationInfo) { + info = applicationInfo; + label = packageManager.getApplicationLabel(applicationInfo).toString(); + } + final ApplicationInfo info; + final String label; + } + + // List of non-system packages that are installed for the current user. + private List getAppInfos(Context context) { + final PackageManager packageManager = context.getPackageManager(); + final List applicationInfos = + packageManager.getInstalledApplications(0 /* flags */); + + final List appInfos = new ArrayList<>(); + for (ApplicationInfo applicationInfo : applicationInfos) { + if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + appInfos.add(new AppInfo(packageManager, applicationInfo)); + } + } + + Collections.sort(appInfos, appInfoComparator); + + return appInfos; + } + + // Parse the raw comma separated package names into a String Set + private Set getGlobalSettingsString(ContentResolver contentResolver, String name) { + final String settingsValue = Settings.Global.getString(contentResolver, name); + if (settingsValue == null) { + return new HashSet<>(); + } + + final Set valueSet = new HashSet<>(Arrays.asList(settingsValue.split(","))); + valueSet.remove(""); + + return valueSet; + } + + private final Comparator appInfoComparator = new Comparator() { + public final int compare(AppInfo a, AppInfo b) { + return Collator.getInstance().compare(a.label, b.label); + } + }; + + @VisibleForTesting + protected ListPreference createListPreference(String packageName, String appName) { + final ListPreference listPreference = new ListPreference(mContext); + + listPreference.setKey(packageName); + listPreference.setTitle(appName); + listPreference.setDialogTitle(mPreferenceTitle); + listPreference.setEntries(mEntryList); + listPreference.setEntryValues(mEntryList); + + // Initialize preference default and summary with the opt in/out choices + // from Settings.Global.GUP_DEV_OPT_(IN|OUT)_APPS + if (mDevOptOutApps.contains(packageName)) { + listPreference.setValue(mPreferenceNative); + listPreference.setSummary(mPreferenceNative); + } else if (mDevOptInApps.contains(packageName)) { + listPreference.setValue(mPreferenceGup); + listPreference.setSummary(mPreferenceGup); + } else { + listPreference.setValue(mPreferenceDefault); + listPreference.setSummary(mPreferenceDefault); + } + + listPreference.setOnPreferenceChangeListener(this); + + return listPreference; + } +} diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 2622eb320e7..447de0049df 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -28,7 +28,6 @@ com.android.settings.bluetooth.DevicePickerFragment com.android.settings.datausage.AppDataUsage com.android.settings.datausage.DataUsageList com.android.settings.datetime.timezone.TimeZoneSettings -com.android.settings.development.gup.GupDashboard com.android.settings.deviceinfo.PrivateVolumeSettings com.android.settings.deviceinfo.PublicVolumeSettings com.android.settings.deviceinfo.StorageProfileFragment diff --git a/tests/robotests/src/com/android/settings/development/GameUpdatePackageDevOptInPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/GameUpdatePackageDevOptInPreferenceControllerTest.java deleted file mode 100644 index 199cad6e191..00000000000 --- a/tests/robotests/src/com/android/settings/development/GameUpdatePackageDevOptInPreferenceControllerTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 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.development; - -import static com.android.settings.development.DevelopmentOptionsActivityRequestCodes.REQUEST_CODE_GUP_DEV_OPT_IN_APPS; -import static com.google.common.truth.Truth.assertThat; -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.app.Activity; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.provider.Settings; - -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public class GameUpdatePackageDevOptInPreferenceControllerTest { - - @Mock - private PreferenceScreen mPreferenceScreen; - @Mock - private DevelopmentSettingsDashboardFragment mFragment; - - private Context mContext; - private Preference mPreference; - private GameUpdatePackageDevOptInPreferenceController mController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mController = spy(new GameUpdatePackageDevOptInPreferenceController(mContext, mFragment)); - mPreference = new Preference(mContext); - mPreference.setKey(mController.getPreferenceKey()); - - when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) - .thenReturn(mPreference); - mController.displayPreference(mPreferenceScreen); - } - - @Test - public void handlePreferenceTreeClick_preferenceClicked_launchActivity() { - final Intent activityStartIntent = new Intent(mContext, AppPicker.class); - doReturn(activityStartIntent).when(mController).getActivityStartIntent(); - mController.handlePreferenceTreeClick(mPreference); - - verify(mFragment).startActivityForResult(activityStartIntent, - REQUEST_CODE_GUP_DEV_OPT_IN_APPS); - } - - @Test - public void updateState_foobarAppSelected_shouldUpdateSummaryWithGUPDevOptInAppLabel() { - final String selectedApp = "foobar"; - final ContentResolver contentResolver = mContext.getContentResolver(); - Settings.Global.putString(contentResolver, - Settings.Global.GUP_DEV_OPT_IN_APPS, selectedApp); - mController.updateState(mPreference); - - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getString(R.string.gup_dev_opt_in_app_set, selectedApp)); - } - - @Test - public void updateState_noAppSelected_shouldUpdateSummaryWithNoAppSelected() { - final String selectedApp = null; - final ContentResolver contentResolver = mContext.getContentResolver(); - Settings.Global.putString(contentResolver, - Settings.Global.GUP_DEV_OPT_IN_APPS, selectedApp); - mController.updateState(mPreference); - - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getString(R.string.gup_dev_opt_in_app_not_set)); - } - - @Test - public void onActivityResult_foobarAppSelected_shouldUpdateSummaryWithGUPDevOptInLabel() { - Intent activityResultIntent = new Intent(mContext, AppPicker.class); - final String appLabel = "foobar"; - activityResultIntent.setAction(appLabel); - final boolean result = mController - .onActivityResult(REQUEST_CODE_GUP_DEV_OPT_IN_APPS, Activity.RESULT_OK, - activityResultIntent); - - assertThat(result).isTrue(); - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getString(R.string.gup_dev_opt_in_app_set, appLabel)); - } - - @Test - public void onActivityResult_badRequestCode_shouldReturnFalse() { - assertThat(mController.onActivityResult( - -1 /* requestCode */, -1 /* resultCode */, null /* intent */)).isFalse(); - } - - @Test - public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() { - mController.onDeveloperOptionsSwitchDisabled(); - - assertThat(mPreference.isEnabled()).isFalse(); - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getString(R.string.gup_dev_opt_in_app_not_set)); - } -} diff --git a/tests/robotests/src/com/android/settings/development/gup/GupPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/gup/GupPreferenceControllerTest.java new file mode 100644 index 00000000000..62e3475bd25 --- /dev/null +++ b/tests/robotests/src/com/android/settings/development/gup/GupPreferenceControllerTest.java @@ -0,0 +1,231 @@ +/* + * Copyright 2019 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.development.gup; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; +import static com.android.settings.testutils.ApplicationTestUtils.buildInfo; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class GupPreferenceControllerTest { + private static final int DEFAULT = 0; + private static final int GUP = 1; + private static final int NATIVE = 2; + private static final String TEST_APP_NAME = "testApp"; + private static final String TEST_PKG_NAME = "testPkg"; + + // Pre-installed Apps in the Mock PackageManager + private static final String APP_1 = "app1"; + private static final String APP_2 = "app2"; + private static final String APP_3 = "app3"; + + @Mock + private PackageManager mPackageManager; + @Mock + private PreferenceScreen mScreen; + + private Context mContext; + private PreferenceGroup mGroup; + private PreferenceManager mPreferenceManager; + private ContentResolver mResolver; + private GupPreferenceController mController; + private CharSequence[] mValueList; + private String mDialogTitle; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mResolver = mContext.getContentResolver(); + mValueList = mContext.getResources().getStringArray(R.array.gup_app_preference_values); + mDialogTitle = mContext.getResources().getString(R.string.gup_app_preference_title); + } + + @Test + public void getAvailability_developmentSettingsEnabled_available() { + loadDefaultConfig(); + Settings.Global.putInt(mResolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailability_developmentSettingsDisabled_disabledDependentSetting() { + loadDefaultConfig(); + Settings.Global.putInt(mResolver, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + public void displayPreference_shouldAddTwoPreferencesAndSortAscendingly() { + mockPackageManager(); + loadDefaultConfig(); + + // Only non-system app has preference + assertThat(mGroup.getPreferenceCount()).isEqualTo(2); + assertThat(mGroup.getPreference(0).getKey()).isEqualTo(APP_1); + assertThat(mGroup.getPreference(1).getKey()).isEqualTo(APP_3); + } + + @Test + public void createPreference_configDefault_shouldSetDefaultAttributes() { + loadDefaultConfig(); + final ListPreference preference = + mController.createListPreference(TEST_PKG_NAME, TEST_APP_NAME); + + assertThat(preference.getKey()).isEqualTo(TEST_PKG_NAME); + assertThat(preference.getTitle()).isEqualTo(TEST_APP_NAME); + assertThat(preference.getDialogTitle()).isEqualTo(mDialogTitle); + assertThat(preference.getEntries()).isEqualTo(mValueList); + assertThat(preference.getEntryValues()).isEqualTo(mValueList); + assertThat(preference.getEntry()).isEqualTo(mValueList[DEFAULT]); + assertThat(preference.getValue()).isEqualTo(mValueList[DEFAULT]); + assertThat(preference.getSummary()).isEqualTo(mValueList[DEFAULT]); + } + + @Test + public void createPreference_configGup_shouldSetGupAttributes() { + loadConfig(TEST_PKG_NAME, ""); + final ListPreference preference = + mController.createListPreference(TEST_PKG_NAME, TEST_APP_NAME); + + assertThat(preference.getKey()).isEqualTo(TEST_PKG_NAME); + assertThat(preference.getTitle()).isEqualTo(TEST_APP_NAME); + assertThat(preference.getDialogTitle()).isEqualTo(mDialogTitle); + assertThat(preference.getEntries()).isEqualTo(mValueList); + assertThat(preference.getEntryValues()).isEqualTo(mValueList); + assertThat(preference.getEntry()).isEqualTo(mValueList[GUP]); + assertThat(preference.getValue()).isEqualTo(mValueList[GUP]); + assertThat(preference.getSummary()).isEqualTo(mValueList[GUP]); + } + + @Test + public void createPreference_configNative_shouldSetNativeAttributes() { + loadConfig("", TEST_PKG_NAME); + final ListPreference preference = + mController.createListPreference(TEST_PKG_NAME, TEST_APP_NAME); + + assertThat(preference.getKey()).isEqualTo(TEST_PKG_NAME); + assertThat(preference.getTitle()).isEqualTo(TEST_APP_NAME); + assertThat(preference.getDialogTitle()).isEqualTo(mDialogTitle); + assertThat(preference.getEntries()).isEqualTo(mValueList); + assertThat(preference.getEntryValues()).isEqualTo(mValueList); + assertThat(preference.getEntry()).isEqualTo(mValueList[NATIVE]); + assertThat(preference.getValue()).isEqualTo(mValueList[NATIVE]); + assertThat(preference.getSummary()).isEqualTo(mValueList[NATIVE]); + } + + @Test + public void onPreferenceChange_selectDefault_shouldUpdateAttributesAndSettingsGlobal() { + loadDefaultConfig(); + final ListPreference preference = + mController.createListPreference(TEST_PKG_NAME, TEST_APP_NAME); + mController.onPreferenceChange(preference, mValueList[DEFAULT]); + + assertThat(preference.getSummary()).isEqualTo(mValueList[DEFAULT]); + assertThat(Settings.Global.getString(mResolver, Settings.Global.GUP_DEV_OPT_IN_APPS)) + .isEqualTo(""); + assertThat(Settings.Global.getString(mResolver, Settings.Global.GUP_DEV_OPT_OUT_APPS)) + .isEqualTo(""); + } + + @Test + public void onPreferenceChange_selectGup_shouldUpdateAttributesAndSettingsGlobal() { + loadDefaultConfig(); + final ListPreference preference = + mController.createListPreference(TEST_PKG_NAME, TEST_APP_NAME); + mController.onPreferenceChange(preference, mValueList[GUP]); + + assertThat(preference.getSummary()).isEqualTo(mValueList[GUP]); + assertThat(Settings.Global.getString(mResolver, Settings.Global.GUP_DEV_OPT_IN_APPS)) + .isEqualTo(TEST_PKG_NAME); + assertThat(Settings.Global.getString(mResolver, Settings.Global.GUP_DEV_OPT_OUT_APPS)) + .isEqualTo(""); + } + + @Test + public void onPreferenceChange_selectNative_shouldUpdateAttributesAndSettingsGlobal() { + loadDefaultConfig(); + final ListPreference preference = + mController.createListPreference(TEST_PKG_NAME, TEST_APP_NAME); + mController.onPreferenceChange(preference, mValueList[NATIVE]); + + assertThat(preference.getSummary()).isEqualTo(mValueList[NATIVE]); + assertThat(Settings.Global.getString(mResolver, Settings.Global.GUP_DEV_OPT_IN_APPS)) + .isEqualTo(""); + assertThat(Settings.Global.getString(mResolver, Settings.Global.GUP_DEV_OPT_OUT_APPS)) + .isEqualTo(TEST_PKG_NAME); + } + + private void mockPackageManager() { + final int uid = mContext.getUserId(); + final ApplicationInfo app1 = buildInfo(uid, APP_1, 0 /* flags */, 0 /* targetSdkVersion */); + final ApplicationInfo app2 = + buildInfo(uid, APP_2, ApplicationInfo.FLAG_SYSTEM, 0 /* targetSdkVersion */); + final ApplicationInfo app3 = buildInfo(uid, APP_3, 0 /* flags */, 0 /* targetSdkVersion */); + + when(mPackageManager.getInstalledApplications(0 /* flags */)) + .thenReturn(Arrays.asList(app3, app2, app1)); + when(mPackageManager.getApplicationLabel(app1)).thenReturn(APP_1); + when(mPackageManager.getApplicationLabel(app2)).thenReturn(APP_2); + when(mPackageManager.getApplicationLabel(app3)).thenReturn(APP_3); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + } + + private void loadDefaultConfig() { loadConfig("", ""); } + + private void loadConfig(String optIn, String optOut) { + Settings.Global.putString(mResolver, Settings.Global.GUP_DEV_OPT_IN_APPS, optIn); + Settings.Global.putString(mResolver, Settings.Global.GUP_DEV_OPT_OUT_APPS, optOut); + + mController = new GupPreferenceController(mContext, "testKey"); + mGroup = spy(new PreferenceCategory(mContext)); + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + when(mGroup.getPreferenceManager()).thenReturn(preferenceManager); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mGroup); + mController.displayPreference(mScreen); + } +}