Remove injecting developer options into Settings itself

- It can improve performance because we use less injected item
- Also use RestrictedPreference instead just checking restrictions,
  so we follow policy transparency.

Note: Renamed DevelopmentSettingsDashboardActivity to
DevelopmentSettingsActivity,
because DevelopmentSettingsDashboardActivity could in disabled state
even after Settings upgrades, use a new name to prevent this issue.

Bug: 311604902
Test: manual - turn on / off Developer Options
Test: unit test
Change-Id: I17be117ae59e59410687e6d08fd5edd034d0508f
This commit is contained in:
Chaohui Wang
2023-11-24 00:15:17 +08:00
parent c9ccf861c6
commit ee7139a889
9 changed files with 177 additions and 128 deletions

View File

@@ -2864,11 +2864,10 @@
</activity> </activity>
<activity <activity
android:name="Settings$DevelopmentSettingsDashboardActivity" android:name="Settings$DevelopmentSettingsActivity"
android:label="@string/development_settings_title" android:label="@string/development_settings_title"
android:icon="@drawable/ic_settings_development" android:icon="@drawable/ic_settings_development"
android:exported="true" android:exported="true">
android:enabled="false">
<intent-filter android:priority="1"> <intent-filter android:priority="1">
<action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" /> <action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" /> <action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
@@ -2879,35 +2878,8 @@
<action android:name="com.android.settings.action.SETTINGS" /> <action android:name="com.android.settings.action.SETTINGS" />
<action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" /> <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
</intent-filter> </intent-filter>
<meta-data android:name="com.android.settings.order" android:value="-40"/>
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.system" />
<meta-data android:name="com.android.settings.summary"
android:resource="@string/summary_empty"/>
<meta-data android:name="com.android.settings.icon"
android:resource="@drawable/ic_settings_development" />
<meta-data android:name="com.android.settings.FRAGMENT_CLASS" <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" /> android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_system"/>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<!-- The opposite of DevelopmentSettingsActivity, it's no-op and only enabled when the real
activity is disabled to be CTS compliant. -->
<activity
android:name=".development.DevelopmentSettingsDisabledActivity"
android:icon="@drawable/ic_settings_development"
android:label="@string/development_settings_title"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@style/Transparent">
<intent-filter android:priority="-1">
<action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity> </activity>
<activity <activity

View File

@@ -100,6 +100,11 @@
android:fragment="com.android.settings.users.UserSettings" android:fragment="com.android.settings.users.UserSettings"
settings:controller="com.android.settings.users.MultiUserPreferenceController"/> settings:controller="com.android.settings.users.MultiUserPreferenceController"/>
<com.android.settings.spa.preference.ComposePreference
android:key="developer_options"
android:order="-40"
settings:controller="com.android.settings.system.DeveloperOptionsController"/>
<Preference <Preference
android:key="reset_dashboard" android:key="reset_dashboard"
android:title="@string/reset_dashboard_title" android:title="@string/reset_dashboard_title"

View File

@@ -134,7 +134,7 @@ public class Settings extends SettingsActivity {
public static class HighPowerApplicationsActivity extends SettingsActivity { /* empty */ } public static class HighPowerApplicationsActivity extends SettingsActivity { /* empty */ }
public static class BackgroundCheckSummaryActivity extends SettingsActivity { /* empty */ } public static class BackgroundCheckSummaryActivity extends SettingsActivity { /* empty */ }
public static class StorageUseActivity extends SettingsActivity { /* empty */ } public static class StorageUseActivity extends SettingsActivity { /* empty */ }
public static class DevelopmentSettingsDashboardActivity extends SettingsActivity { /* empty */ } public static class DevelopmentSettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilitySettingsActivity extends SettingsActivity { /* empty */ } public static class AccessibilitySettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilityDetailsSettingsActivity extends SettingsActivity { /* empty */ } public static class AccessibilityDetailsSettingsActivity extends SettingsActivity { /* empty */ }
public static class CaptioningSettingsActivity extends SettingsActivity { /* empty */ } public static class CaptioningSettingsActivity extends SettingsActivity { /* empty */ }

View File

@@ -48,7 +48,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@@ -70,7 +69,6 @@ import com.android.settings.wfd.WifiDisplaySettings;
import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.core.instrumentation.SharedPreferencesLogger; import com.android.settingslib.core.instrumentation.SharedPreferencesLogger;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.DashboardCategory;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -174,8 +172,6 @@ public class SettingsActivity extends SettingsBaseActivity
private CharSequence mInitialTitle; private CharSequence mInitialTitle;
private int mInitialTitleResId; private int mInitialTitleResId;
private BroadcastReceiver mDevelopmentSettingsListener;
private boolean mBatteryPresent = true; private boolean mBatteryPresent = true;
private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() { private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
@Override @Override
@@ -614,15 +610,6 @@ public class SettingsActivity extends SettingsBaseActivity
super.onResume(); super.onResume();
setActionBarStatus(); setActionBarStatus();
mDevelopmentSettingsListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateTilesList();
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(mDevelopmentSettingsListener,
new IntentFilter(DevelopmentSettingsEnabler.DEVELOPMENT_SETTINGS_CHANGED_ACTION));
registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
updateTilesList(); updateTilesList();
@@ -631,8 +618,6 @@ public class SettingsActivity extends SettingsBaseActivity
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mDevelopmentSettingsListener);
mDevelopmentSettingsListener = null;
unregisterReceiver(mBatteryInfoReceiver); unregisterReceiver(mBatteryInfoReceiver);
} }
@@ -781,13 +766,6 @@ public class SettingsActivity extends SettingsBaseActivity
Utils.isBandwidthControlEnabled(), isAdmin) Utils.isBandwidthControlEnabled(), isAdmin)
|| somethingChanged; || somethingChanged;
final boolean showDev = DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this)
&& !Utils.isMonkeyRunning();
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.DevelopmentSettingsDashboardActivity.class.getName()),
showDev, isAdmin)
|| somethingChanged;
somethingChanged = setTileEnabled(changedList, new ComponentName(packageName, somethingChanged = setTileEnabled(changedList, new ComponentName(packageName,
Settings.WifiDisplaySettingsActivity.class.getName()), Settings.WifiDisplaySettingsActivity.class.getName()),
WifiDisplaySettings.isAvailable(this), isAdmin) WifiDisplaySettings.isAvailable(this), isAdmin)

View File

@@ -47,6 +47,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -230,6 +231,12 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
getActivity().finish(); getActivity().finish();
return; return;
} }
Context context = requireContext();
if (!DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)) {
Toast.makeText(context, R.string.dev_settings_disabled_warning, Toast.LENGTH_SHORT)
.show();
finish();
}
} }
@Override @Override

View File

@@ -1,32 +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.development;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import com.android.settings.R;
public class DevelopmentSettingsDisabledActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(this, R.string.dev_settings_disabled_warning, Toast.LENGTH_SHORT).show();
finish();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 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.system
import android.app.settings.SettingsEnums
import android.content.Context
import android.os.Build
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.core.SubSettingLauncher
import com.android.settings.development.DevelopmentSettingsDashboardFragment
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsIcon
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
class DeveloperOptionsController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
override fun getAvailabilityStatus() = AVAILABLE
private val isDevelopmentSettingsEnabledFlow = context.settingsGlobalBooleanFlow(
name = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
defaultValue = Build.IS_ENG,
)
@Composable
override fun Content() {
val isDevelopmentSettingsEnabled by isDevelopmentSettingsEnabledFlow
.collectAsStateWithLifecycle(initialValue = false)
if (isDevelopmentSettingsEnabled) {
DeveloperOptionsPreference()
}
}
@VisibleForTesting
@Composable
fun DeveloperOptionsPreference() {
RestrictedPreference(
model = object : PreferenceModel {
override val title =
stringResource(com.android.settingslib.R.string.development_settings_title)
override val icon = @Composable {
SettingsIcon(ImageVector.vectorResource(R.drawable.ic_settings_development))
}
override val onClick = {
SubSettingLauncher(mContext).apply {
setDestination(DevelopmentSettingsDashboardFragment::class.qualifiedName)
setSourceMetricsCategory(SettingsEnums.SETTINGS_SYSTEM_CATEGORY)
}.launch()
}
},
restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_DEBUGGING_FEATURES)),
)
}
}

View File

@@ -1,43 +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.development;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import com.android.settings.R;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowToast;
@RunWith(RobolectricTestRunner.class)
public class DevelopmentSettingsDisabledActivityTest {
@Test
public void launchActivity_shouldShowToast() {
Robolectric.setupActivity(DevelopmentSettingsDisabledActivity.class);
final Context context = RuntimeEnvironment.application;
assertThat(ShadowToast.getTextOfLatestToast())
.isEqualTo(context.getString(R.string.dev_settings_disabled_warning));
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 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.system
import android.content.Context
import android.content.Intent
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.SettingsActivity
import com.android.settings.development.DevelopmentSettingsDashboardFragment
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class DeveloperOptionsControllerTest {
@get:Rule
val composeTestRule = createComposeRule()
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
doNothing().whenever(mock).startActivity(any())
}
private val controller = DeveloperOptionsController(context, TEST_KEY)
@Test
fun title_isDisplayed() {
composeTestRule.setContent {
controller.DeveloperOptionsPreference()
}
composeTestRule.onNodeWithText(
context.getString(com.android.settingslib.R.string.development_settings_title)
).assertIsDisplayed()
}
@Test
fun onClick_launchDevelopmentSettingsDashboardFragment() {
composeTestRule.setContent {
controller.DeveloperOptionsPreference()
}
composeTestRule.onNodeWithText(
context.getString(com.android.settingslib.R.string.development_settings_title)
).performClick()
val intent = argumentCaptor<Intent> {
verify(context).startActivity(capture())
}.firstValue
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DevelopmentSettingsDashboardFragment::class.qualifiedName)
}
private companion object {
const val TEST_KEY = "test_key"
}
}