diff --git a/robolectric_tests/Android.bp b/robolectric_tests/Android.bp index bf323623b2..9ed26ff89e 100644 --- a/robolectric_tests/Android.bp +++ b/robolectric_tests/Android.bp @@ -45,9 +45,14 @@ android_robolectric_test { java_resources: [":launcher3-robolectric-resources"], static_libs: [ "truth-prebuilt", + "androidx.test.espresso.contrib", + "androidx.test.espresso.core", + "androidx.test.espresso.intents", + "androidx.test.ext.junit", "androidx.test.runner", "androidx.test.rules", "mockito-robolectric-prebuilt", + "SystemUISharedLib", ], robolectric_prebuilt_version: "4.5.1", instrumentation_for: "Launcher3", diff --git a/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java b/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java new file mode 100644 index 0000000000..85bf28e0b9 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/settings/SettingsActivityTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.settings; + +import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem; +import static androidx.test.espresso.intent.Intents.intended; +import static androidx.test.espresso.intent.matcher.BundleMatchers.hasEntry; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; +import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT; +import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARGS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.equalTo; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; +import androidx.test.core.app.ActivityScenario; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.espresso.intent.Intents; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.launcher3.R; +import com.android.systemui.shared.plugins.PluginPrefs; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SettingsActivityTest { + + private Context mApplicationContext; + + @Before + public void setUp() { + mApplicationContext = ApplicationProvider.getApplicationContext(); + Intents.init(); + } + + @After + public void tearDown() { + Intents.release(); + } + + @Test + public void testSettings_aboutTap_launchesActivity() { + ActivityScenario.launch(SettingsActivity.class); + onView(withId(R.id.recycler_view)).perform( + actionOnItem(hasDescendant(withText("About")), click())); + + intended(allOf( + hasComponent(SettingsActivity.class.getName()), + hasExtra( + equalTo(EXTRA_FRAGMENT_ARGS), + hasEntry(ARG_PREFERENCE_ROOT, "about_screen")))); + } + + @Test + public void testSettings_developerOptionsTap_launchesActivityWithFragment() { + PluginPrefs.setHasPlugins(mApplicationContext); + ActivityScenario.launch(SettingsActivity.class); + onView(withId(R.id.recycler_view)).perform( + actionOnItem(hasDescendant(withText("Developer Options")), click())); + + intended(allOf( + hasComponent(SettingsActivity.class.getName()), + hasExtra(EXTRA_FRAGMENT, DeveloperOptionsFragment.class.getName()))); + } + + @Test + public void testSettings_aboutScreenIntent() { + Bundle fragmentArgs = new Bundle(); + fragmentArgs.putString(ARG_PREFERENCE_ROOT, "about_screen"); + + Intent intent = new Intent(mApplicationContext, SettingsActivity.class) + .putExtra(EXTRA_FRAGMENT_ARGS, fragmentArgs); + ActivityScenario.launch(intent); + + onView(withText("About")).check(matches(isDisplayed())); + onView(withText("Version")).check(matches(isDisplayed())); + } + + @Test + public void testSettings_developerOptionsFragmentIntent() { + Intent intent = new Intent(mApplicationContext, SettingsActivity.class) + .putExtra(EXTRA_FRAGMENT, DeveloperOptionsFragment.class.getName()); + ActivityScenario.launch(intent); + + onView(withText("Developer Options")).check(matches(isDisplayed())); + onView(withId(R.id.filter_box)).check(matches(isDisplayed())); + } + + @Test + public void testSettings_intentWithUnknownFragment() { + String fragmentClass = PreferenceFragmentCompat.class.getName(); + Intent intent = new Intent(mApplicationContext, SettingsActivity.class) + .putExtra(EXTRA_FRAGMENT, fragmentClass); + + try { + ActivityScenario.launch(intent); + Assert.fail("Should have thrown an IllegalArgumentException."); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage()).contains(fragmentClass); + } + } +} diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index c6b0b13db3..e2a69ff7a2 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -104,6 +104,10 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat { initFlags(); loadPluginPrefs(); maybeAddSandboxCategory(); + + if (getActivity() != null) { + getActivity().setTitle("Developer Options"); + } } private void filterPreferences(String query, PreferenceGroup pg) { diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index 883ff75e37..85c249261f 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -20,11 +20,13 @@ import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTIO import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; +import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; @@ -46,6 +48,9 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import java.util.Collections; +import java.util.List; + /** * Settings activity for Launcher. Currently implements the following setting: Allow rotation */ @@ -53,6 +58,10 @@ public class SettingsActivity extends FragmentActivity implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback, SharedPreferences.OnSharedPreferenceChangeListener{ + /** List of fragments that can be hosted by this activity. */ + private static final List VALID_PREFERENCE_FRAGMENTS = Collections.singletonList( + DeveloperOptionsFragment.class.getName()); + private static final String DEVELOPER_OPTIONS_KEY = "pref_developer_options"; private static final String FLAGS_PREFERENCE_KEY = "flag_toggler"; @@ -63,20 +72,30 @@ public class SettingsActivity extends FragmentActivity private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600; public static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted"; + @VisibleForTesting + static final String EXTRA_FRAGMENT = ":settings:fragment"; + @VisibleForTesting + static final String EXTRA_FRAGMENT_ARGS = ":settings:fragment_args"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { - Bundle args = new Bundle(); - String prefKey = getIntent().getStringExtra(EXTRA_FRAGMENT_ARG_KEY); + Intent intent = getIntent(); + Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS); + if (args == null) { + args = new Bundle(); + } + + String prefKey = intent.getStringExtra(EXTRA_FRAGMENT_ARG_KEY); if (!TextUtils.isEmpty(prefKey)) { args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey); } final FragmentManager fm = getSupportFragmentManager(); final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), - getString(R.string.settings_fragment_name)); + getPreferenceFragment()); f.setArguments(args); // Display the fragment as the main content. fm.beginTransaction().replace(android.R.id.content, f).commit(); @@ -84,22 +103,45 @@ public class SettingsActivity extends FragmentActivity Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this); } + /** + * Obtains the preference fragment to instantiate in this activity. + * + * @return the preference fragment class + * @throws IllegalArgumentException if the fragment is unknown to this activity + */ + private String getPreferenceFragment() { + String preferenceFragment = getIntent().getStringExtra(EXTRA_FRAGMENT); + String defaultFragment = getString(R.string.settings_fragment_name); + + if (TextUtils.isEmpty(preferenceFragment)) { + return defaultFragment; + } else if (!preferenceFragment.equals(defaultFragment) + && !VALID_PREFERENCE_FRAGMENTS.contains(preferenceFragment)) { + throw new IllegalArgumentException( + "Invalid fragment for this activity: " + preferenceFragment); + } else { + return preferenceFragment; + } + } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { } - private boolean startFragment(String fragment, Bundle args, String key) { + private boolean startPreference(String fragment, Bundle args, String key) { if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) { // Sometimes onClick can come after onPause because of being posted on the handler. - // Skip starting new fragments in that case. + // Skip starting new preferences in that case. return false; } final FragmentManager fm = getSupportFragmentManager(); final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment); - f.setArguments(args); if (f instanceof DialogFragment) { - ((DialogFragment) f).show(getSupportFragmentManager(), key); + f.setArguments(args); + ((DialogFragment) f).show(fm, key); } else { - fm.beginTransaction().replace(android.R.id.content, f).addToBackStack(key).commit(); + startActivity(new Intent(this, SettingsActivity.class) + .putExtra(EXTRA_FRAGMENT, fragment) + .putExtra(EXTRA_FRAGMENT_ARGS, args)); } return true; } @@ -107,14 +149,14 @@ public class SettingsActivity extends FragmentActivity @Override public boolean onPreferenceStartFragment( PreferenceFragmentCompat preferenceFragment, Preference pref) { - return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey()); + return startPreference(pref.getFragment(), pref.getExtras(), pref.getKey()); } @Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) { Bundle args = new Bundle(); args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey()); - return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey()); + return startPreference(getString(R.string.settings_fragment_name), args, pref.getKey()); } /** @@ -148,6 +190,10 @@ public class SettingsActivity extends FragmentActivity screen.removePreference(preference); } } + + if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) { + getActivity().setTitle(getPreferenceScreen().getTitle()); + } } @Override