diff --git a/res/values/strings.xml b/res/values/strings.xml index 3eb67ab33e7..257d9522796 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9482,6 +9482,13 @@ other {# apps used memory in the last {time}} } + + Enable memory usage profiling + + Memory usage profiling requires additional system resources. + + Memory profiling disabled + Frequency diff --git a/res/xml/process_stats_summary.xml b/res/xml/process_stats_summary.xml index 3b3271d769a..9def18fa7b8 100644 --- a/res/xml/process_stats_summary.xml +++ b/res/xml/process_stats_summary.xml @@ -19,45 +19,52 @@ android:title="@string/app_memory_use" android:key="app_list"> + + + android:title="@string/average_memory_use" + android:key="memory_info"> - + - + - + - + - + - + - + - + + diff --git a/src/com/android/settings/applications/ProcessStatsSummary.java b/src/com/android/settings/applications/ProcessStatsSummary.java index 4044794e719..ef76cd5840b 100644 --- a/src/com/android/settings/applications/ProcessStatsSummary.java +++ b/src/com/android/settings/applications/ProcessStatsSummary.java @@ -16,20 +16,26 @@ package com.android.settings.applications; import android.app.settings.SettingsEnums; +import android.content.ContentResolver; import android.content.Context; import android.icu.text.MessageFormat; import android.os.Bundle; +import android.os.Flags; +import android.provider.Settings; import android.text.format.Formatter; import android.text.format.Formatter.BytesResult; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceClickListener; +import androidx.preference.PreferenceCategory; +import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SummaryPreference; import com.android.settings.Utils; import com.android.settings.applications.ProcStatsData.MemInfo; import com.android.settings.core.SubSettingLauncher; +import com.android.settings.development.DisableDevSettingsDialogFragment; import java.util.HashMap; import java.util.Locale; @@ -37,6 +43,8 @@ import java.util.Map; public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenceClickListener { + private static final String KEY_PREF_SCREEN = "app_list"; + private static final String KEY_MEMORY_INFO_PREF_GROUP = "memory_info"; private static final String KEY_STATUS_HEADER = "status_header"; private static final String KEY_PERFORMANCE = "performance"; @@ -44,7 +52,9 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc private static final String KEY_AVERAGY_USED = "average_used"; private static final String KEY_FREE = "free"; private static final String KEY_APP_LIST = "apps_list"; + private static final String KEY_FORCE_ENABLE_PSS_PROFILING = "force_enable_pss_profiling"; + private PreferenceCategory mMemoryInfoPrefCategory; private SummaryPreference mSummaryPref; private Preference mPerformance; @@ -52,12 +62,14 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc private Preference mAverageUsed; private Preference mFree; private Preference mAppListPreference; + private SwitchPreference mForceEnablePssProfiling; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); addPreferencesFromResource(R.xml.process_stats_summary); + mMemoryInfoPrefCategory = (PreferenceCategory) findPreference(KEY_MEMORY_INFO_PREF_GROUP); mSummaryPref = (SummaryPreference) findPreference(KEY_STATUS_HEADER); mPerformance = findPreference(KEY_PERFORMANCE); mTotalMemory = findPreference(KEY_TOTAL_MEMORY); @@ -65,11 +77,37 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc mFree = findPreference(KEY_FREE); mAppListPreference = findPreference(KEY_APP_LIST); mAppListPreference.setOnPreferenceClickListener(this); + + // This preference is only applicable if the flag for PSS deprecation in AppProfiler is + // enabled. Otherwise, it can immediately be hidden. + mForceEnablePssProfiling = + (SwitchPreference) findPreference(KEY_FORCE_ENABLE_PSS_PROFILING); + if (Flags.removeAppProfilerPssCollection()) { + mForceEnablePssProfiling.setOnPreferenceClickListener(this); + // Make the toggle reflect the current state of the global setting. + mForceEnablePssProfiling.setChecked(isPssProfilingForceEnabled(getContext())); + } else { + mForceEnablePssProfiling.setVisible(false); + } + } + + private void refreshPreferences() { + // The memory fields should be static if the flag is not enabled. + if (!Flags.removeAppProfilerPssCollection()) { + return; + } + mMemoryInfoPrefCategory.setVisible(mForceEnablePssProfiling.isChecked()); } @Override public void refreshUi() { Context context = getContext(); + refreshPreferences(); + + // If PSS collection is not enabled, none of the following work needs to be done. + if (Flags.removeAppProfilerPssCollection() && !isPssProfilingForceEnabled(context)) { + return; + } MemInfo memInfo = mStatsManager.getMemInfo(); @@ -100,7 +138,8 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc String durationString = getString(sDurationLabels[mDurationIndex]); int numApps = mStatsManager.getEntries().size(); MessageFormat msgFormat = new MessageFormat( - getResources().getString(R.string.memory_usage_apps_summary), Locale.getDefault()); + getResources().getString(R.string.memory_usage_apps_summary), + Locale.getDefault()); Map arguments = new HashMap<>(); arguments.put("count", numApps); arguments.put("time", durationString); @@ -131,7 +170,34 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc .setSourceMetricsCategory(getMetricsCategory()) .launch(); return true; + } else if (preference == mForceEnablePssProfiling) { + DisableDevSettingsDialogFragment.show(this); } return false; } + + private boolean isPssProfilingForceEnabled(Context context) { + ContentResolver cr = context.getContentResolver(); + return Settings.Global.getInt(cr, Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1; + } + + /** + * Called when the reboot confirmation button is clicked. + */ + public void onRebootDialogConfirmed() { + Context context = getContext(); + ContentResolver cr = context.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.FORCE_ENABLE_PSS_PROFILING, + mForceEnablePssProfiling.isChecked() ? 1 : 0); + refreshPreferences(); + } + + /** + * Called when the reboot deny button is clicked. + */ + public void onRebootDialogCanceled() { + // Set the toggle to reflect the state of the setting, which should not have changed. + mForceEnablePssProfiling.setChecked(isPssProfilingForceEnabled(getContext())); + } + } diff --git a/src/com/android/settings/development/DisableDevSettingsDialogFragment.java b/src/com/android/settings/development/DisableDevSettingsDialogFragment.java index 224768b0561..4355f277cb3 100644 --- a/src/com/android/settings/development/DisableDevSettingsDialogFragment.java +++ b/src/com/android/settings/development/DisableDevSettingsDialogFragment.java @@ -29,6 +29,8 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.ProcessStatsSummary; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; public class DisableDevSettingsDialogFragment extends InstrumentedDialogFragment @@ -42,7 +44,7 @@ public class DisableDevSettingsDialogFragment extends InstrumentedDialogFragment return dialog; } - public static void show(DevelopmentSettingsDashboardFragment host) { + public static void show(SettingsPreferenceFragment host) { final DisableDevSettingsDialogFragment dialog = new DisableDevSettingsDialogFragment(); dialog.setTargetFragment(host, 0 /* requestCode */); // We need to handle data changes and switch state based on which button user clicks, @@ -75,18 +77,31 @@ public class DisableDevSettingsDialogFragment extends InstrumentedDialogFragment @Override public void onClick(DialogInterface dialog, int which) { Fragment fragment = getTargetFragment(); - if (!(fragment instanceof DevelopmentSettingsDashboardFragment)){ + if (!(fragment instanceof DevelopmentSettingsDashboardFragment) + && !(fragment instanceof ProcessStatsSummary)) { Log.e(TAG, "getTargetFragment return unexpected type"); } - final DevelopmentSettingsDashboardFragment host = - (DevelopmentSettingsDashboardFragment) fragment; - if (which == DialogInterface.BUTTON_POSITIVE) { - host.onDisableDevelopmentOptionsConfirmed(); - PowerManager pm = getContext().getSystemService(PowerManager.class); - pm.reboot(null); - } else { - host.onDisableDevelopmentOptionsRejected(); + if (fragment instanceof DevelopmentSettingsDashboardFragment) { + final DevelopmentSettingsDashboardFragment host = + (DevelopmentSettingsDashboardFragment) fragment; + if (which == DialogInterface.BUTTON_POSITIVE) { + host.onDisableDevelopmentOptionsConfirmed(); + PowerManager pm = getContext().getSystemService(PowerManager.class); + pm.reboot(null); + } else { + host.onDisableDevelopmentOptionsRejected(); + } + } else if (fragment instanceof ProcessStatsSummary) { + final ProcessStatsSummary host = + (ProcessStatsSummary) fragment; + if (which == DialogInterface.BUTTON_POSITIVE) { + host.onRebootDialogConfirmed(); + PowerManager pm = getContext().getSystemService(PowerManager.class); + pm.reboot(null); + } else { + host.onRebootDialogCanceled(); + } } } } diff --git a/src/com/android/settings/development/MemoryUsagePreferenceController.java b/src/com/android/settings/development/MemoryUsagePreferenceController.java index 1b20e70a578..ca8effa4a32 100644 --- a/src/com/android/settings/development/MemoryUsagePreferenceController.java +++ b/src/com/android/settings/development/MemoryUsagePreferenceController.java @@ -17,6 +17,8 @@ package com.android.settings.development; import android.content.Context; +import android.os.Flags; +import android.provider.Settings; import android.text.format.Formatter; import androidx.annotation.VisibleForTesting; @@ -65,9 +67,13 @@ public class MemoryUsagePreferenceController extends DeveloperOptionsPreferenceC (long) memInfo.realUsedRam); final String totalResult = Formatter.formatShortFileSize(mContext, (long) memInfo.realTotalRam); - ThreadUtils.postOnMainThread( - () -> mPreference.setSummary(mContext.getString(R.string.memory_summary, - usedResult, totalResult))); + boolean displayMemorySummary = !Flags.removeAppProfilerPssCollection(); + displayMemorySummary |= Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1; + String summary = displayMemorySummary + ? mContext.getString(R.string.memory_summary, usedResult, totalResult) + : mContext.getString(R.string.pss_profiling_disabled); + ThreadUtils.postOnMainThread(() -> mPreference.setSummary(summary)); }); } diff --git a/tests/uitests/src/com/android/settings/ui/MemorySettingsTest.kt b/tests/uitests/src/com/android/settings/ui/MemorySettingsTest.kt new file mode 100644 index 00000000000..312e2c62bf2 --- /dev/null +++ b/tests/uitests/src/com/android/settings/ui/MemorySettingsTest.kt @@ -0,0 +1,76 @@ +/* + * 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.ui + +import android.os.Flags +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import com.android.settings.ui.testutils.SettingsTestUtils.assertHasTexts +import com.android.settings.ui.testutils.SettingsTestUtils.clickObject +import com.android.settings.ui.testutils.SettingsTestUtils.startMainActivityFromHomeScreen +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class MemorySettingsTest { + private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + @Before + fun setUp() { + device.startMainActivityFromHomeScreen(Settings.ACTION_DEVICE_INFO_SETTINGS) + device.assertHasTexts(listOf(BUILD_NUMBER)) + repeat(7) { // Enable development mode + device.clickObject(By.text(BUILD_NUMBER)) + } + device.startMainActivityFromHomeScreen(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS) + device.clickObject(By.text(MEMORY_PAGE)) + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) + fun memoryPageIfPssFlagDisabled() { + device.assertHasTexts(ON_SCREEN_TEXTS_DEFAULT) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) + fun memoryPageIfPssFlagEnabled() { + device.assertHasTexts(ON_SCREEN_TEXTS_PSS_PROFILING_DISABLED) + } + + private companion object { + private const val BUILD_NUMBER = "Build number" + private const val MEMORY_PAGE = "Memory" + val ON_SCREEN_TEXTS_DEFAULT = listOf( + "Performance", + "Total memory", + "Average used (%)", + "Free", + ) + val ON_SCREEN_TEXTS_PSS_PROFILING_DISABLED = listOf( + "Enable memory usage profiling", + ) + } +}