diff options
60 files changed, 4419 insertions, 2523 deletions
diff --git a/apex/media/framework/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java index d059c670ccb6..159e8e551d11 100644 --- a/apex/media/framework/java/android/media/MediaController2.java +++ b/apex/media/framework/java/android/media/MediaController2.java @@ -594,7 +594,6 @@ public class MediaController2 implements AutoCloseable { if (DEBUG) { Log.d(TAG, "onServiceConnected " + name + " " + this); } - // Sanity check if (!mSessionToken.getPackageName().equals(name.getPackageName())) { Log.wtf(TAG, "Expected connection to " + mSessionToken.getPackageName() + " but is connected to " + name); diff --git a/api/current.txt b/api/current.txt index 723ffec5916b..6cc6956b8aba 100644 --- a/api/current.txt +++ b/api/current.txt @@ -46824,6 +46824,7 @@ package android.telephony { field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool"; field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool"; field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool"; + field public static final String KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL = "hide_tty_hco_vco_with_rtt"; field public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls"; field public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool"; field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool"; @@ -46890,7 +46891,11 @@ package android.telephony { field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array"; field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool"; field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool"; + field public static final String KEY_RTT_AUTO_UPGRADE_BOOL = "rtt_auto_upgrade_bool"; + field public static final String KEY_RTT_DOWNGRADE_SUPPORTED_BOOL = "rtt_downgrade_supported_bool"; field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool"; + field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; + field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 46c377037542..8334b6b4db90 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -315,6 +315,7 @@ void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { } int StatsPullerManager::ForceClearPullerCache() { + std::lock_guard<std::mutex> _l(mLock); int totalCleared = 0; for (const auto& pulledAtom : kAllPullAtomInfo) { totalCleared += pulledAtom.second->ForceClearCache(); @@ -323,6 +324,7 @@ int StatsPullerManager::ForceClearPullerCache() { } int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) { + std::lock_guard<std::mutex> _l(mLock); int totalCleared = 0; for (const auto& pulledAtom : kAllPullAtomInfo) { totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs); diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java index 1015724a485d..23ad9353a206 100644 --- a/core/java/android/hardware/face/FaceSensorProperties.java +++ b/core/java/android/hardware/face/FaceSensorProperties.java @@ -40,21 +40,27 @@ public class FaceSensorProperties implements Parcelable { * from above the HAL. */ public final boolean supportsSelfIllumination; + /** + * Maximum number of enrollments a user/profile can have. + */ + public final int maxTemplatesAllowed; /** * Initializes SensorProperties with specified values */ public FaceSensorProperties(int sensorId, boolean supportsFaceDetection, - boolean supportsSelfIllumination) { + boolean supportsSelfIllumination, int maxTemplatesAllowed) { this.sensorId = sensorId; this.supportsFaceDetection = supportsFaceDetection; this.supportsSelfIllumination = supportsSelfIllumination; + this.maxTemplatesAllowed = maxTemplatesAllowed; } protected FaceSensorProperties(Parcel in) { sensorId = in.readInt(); supportsFaceDetection = in.readBoolean(); supportsSelfIllumination = in.readBoolean(); + maxTemplatesAllowed = in.readInt(); } public static final Creator<FaceSensorProperties> CREATOR = @@ -80,5 +86,6 @@ public class FaceSensorProperties implements Parcelable { dest.writeInt(sensorId); dest.writeBoolean(supportsFaceDetection); dest.writeBoolean(supportsSelfIllumination); + dest.writeInt(maxTemplatesAllowed); } } diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java index b28551c52b9e..2fd006809046 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java @@ -48,21 +48,25 @@ public class FingerprintSensorProperties implements Parcelable { // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT // cannot be checked public final boolean resetLockoutRequiresHardwareAuthToken; + // Maximum number of enrollments a user/profile can have. + public final int maxTemplatesAllowed; /** * Initializes SensorProperties with specified values */ public FingerprintSensorProperties(int sensorId, @SensorType int sensorType, - boolean resetLockoutRequiresHardwareAuthToken) { + boolean resetLockoutRequiresHardwareAuthToken, int maxTemplatesAllowed) { this.sensorId = sensorId; this.sensorType = sensorType; this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken; + this.maxTemplatesAllowed = maxTemplatesAllowed; } protected FingerprintSensorProperties(Parcel in) { sensorId = in.readInt(); sensorType = in.readInt(); resetLockoutRequiresHardwareAuthToken = in.readBoolean(); + maxTemplatesAllowed = in.readInt(); } public static final Creator<FingerprintSensorProperties> CREATOR = @@ -88,5 +92,6 @@ public class FingerprintSensorProperties implements Parcelable { dest.writeInt(sensorId); dest.writeInt(sensorType); dest.writeBoolean(resetLockoutRequiresHardwareAuthToken); + dest.writeInt(maxTemplatesAllowed); } } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 4303d705f945..100b4fdbf446 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -531,7 +531,7 @@ public class ViewDebug { @UnsupportedAppUsage static void dispatchCommand(View view, String command, String parameters, OutputStream clientStream) throws IOException { - // Paranoid but safe... + // Just being cautious... view = view.getRootView(); if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) { diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 8b4fddbec03c..6314fcbde333 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -395,6 +395,11 @@ public final class SystemUiDeviceConfigFlags { public static final String PIP_PINCH_RESIZE = "pip_pinch_resize"; /** + * (boolean) Whether to enable stashing for PIP. + */ + public static final String PIP_STASHING = "pip_stashing"; + + /** * (float) Bottom height in DP for Back Gesture. */ public static final String BACK_GESTURE_BOTTOM_HEIGHT = "back_gesture_bottom_height"; diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 41bf74ce9d61..e58990eff2c8 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -73,6 +73,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.KeyValueListParser; import android.util.Log; @@ -14126,7 +14127,11 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void dumpConstantsLocked(PrintWriter pw) { - mConstants.dumpLocked(pw); + final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " "); + iPw.println("BatteryStats constants:"); + iPw.increaseIndent(); + mConstants.dumpLocked(iPw); + iPw.decreaseIndent(); } @GuardedBy("this") @@ -16077,6 +16082,7 @@ public class BatteryStatsImpl extends BatteryStats { mCameraOnTimer.logState(pr, " "); } super.dumpLocked(context, pw, flags, reqUid, histStart); + pw.print("Total cpu time reads: "); pw.println(mNumSingleUidCpuTimeReads); pw.print("Batched cpu time reads: "); @@ -16087,5 +16093,8 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(mNumAllUidCpuTimeReads); pw.print("UIDs removed since the later of device start or stats reset: "); pw.println(mNumUidsRemoved); + + pw.println(); + dumpConstantsLocked(pw); } } diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index ac08d96ab303..050c1c4b4df5 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -239,7 +239,9 @@ <!-- Old synonym for "privileged". Deprecated in API level 23. --> <flag name="system" value="0x10" /> <!-- Additional flag from base permission type: this permission can also - (optionally) be granted to development applications. --> + (optionally) be granted to development applications. Although undocumented, the + permission state used to be shared by all users (including future users), but it is + managed per-user since API level 31. --> <flag name="development" value="0x20" /> <!-- Additional flag from base permission type: this permission is closely associated with an app op for controlling access. --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index d0051db9e9fc..9047b71253da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -46,6 +46,12 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { final SurfaceControl.Transaction t = mTransactionPool.acquire(); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", taskInfo.taskId); + // Reset several properties back to fullscreen (PiP, for example, leaves all these + // properties in a bad state). + t.setPosition(leash, 0, 0); + t.setWindowCrop(leash, null); + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); t.show(leash); t.apply(); } diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 029e61492b6d..011e835e02ec 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -210,7 +210,7 @@ public final class MediaBrowser { public void disconnect() { // It's ok to call this any state, because allowing this lets apps not have // to check isConnected() unnecessarily. They won't appreciate the extra - // assertions for this. We do everything we can here to go back to a sane state. + // assertions for this. We do everything we can here to go back to a valid state. mState = CONNECT_STATE_DISCONNECTING; mHandler.post(new Runnable() { @Override diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 26a0ad917c99..c0509445a54a 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -44967,6 +44967,7 @@ package android.telephony { field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool"; field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool"; field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool"; + field public static final String KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL = "hide_tty_hco_vco_with_rtt"; field public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls"; field public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool"; field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool"; @@ -45033,7 +45034,11 @@ package android.telephony { field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array"; field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool"; field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool"; + field public static final String KEY_RTT_AUTO_UPGRADE_BOOL = "rtt_auto_upgrade_bool"; + field public static final String KEY_RTT_DOWNGRADE_SUPPORTED_BOOL = "rtt_downgrade_supported_bool"; field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool"; + field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; + field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java index e316139326a9..078196e18b88 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java @@ -305,14 +305,14 @@ public class SystemBarConfigs { e.printStackTrace(); } - if (!mTopNavBarEnabled && notificationPanelMediatorUsed.isAssignableFrom( - TopNotificationPanelViewMediator.class)) { + if (!mTopNavBarEnabled && TopNotificationPanelViewMediator.class.isAssignableFrom( + notificationPanelMediatorUsed)) { throw new RuntimeException( "Top System Bar must be enabled to use " + notificationPanelMediatorName); } - if (!mBottomNavBarEnabled && notificationPanelMediatorUsed.isAssignableFrom( - BottomNotificationPanelViewMediator.class)) { + if (!mBottomNavBarEnabled && BottomNotificationPanelViewMediator.class.isAssignableFrom( + notificationPanelMediatorUsed)) { throw new RuntimeException("Bottom System Bar must be enabled to use " + notificationPanelMediatorName); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java index 7eb41de82ae0..0c62f8b4a22b 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/SystemBarConfigsTest.java @@ -33,7 +33,14 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarSystemUiTest; +import com.android.systemui.car.notification.NotificationPanelViewController; +import com.android.systemui.car.notification.NotificationPanelViewMediator; +import com.android.systemui.car.notification.PowerManagerHelper; +import com.android.systemui.car.notification.TopNotificationPanelViewMediator; +import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.Before; import org.junit.Test; @@ -55,8 +62,6 @@ public class SystemBarConfigsTest extends SysuiTestCase { private SystemBarConfigs mSystemBarConfigs; @Mock private Resources mResources; - @Mock - private CarNavigationBarView mCarNavigationBarView; @Before public void setUp() { @@ -109,6 +114,33 @@ public class SystemBarConfigsTest extends SysuiTestCase { } @Test + public void onInit_topNotifPanelViewMediatorUsed_topBarEnabled_doesNotThrowException() { + when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(true); + when(mResources.getString(R.string.config_notificationPanelViewMediator)).thenReturn( + TestTopNotificationPanelViewMediator.class.getName()); + + mSystemBarConfigs = new SystemBarConfigs(mResources); + } + + @Test(expected = RuntimeException.class) + public void onInit_topNotifPanelViewMediatorUsed_topBarNotEnabled_throwsRuntimeException() { + when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(false); + when(mResources.getString(R.string.config_notificationPanelViewMediator)).thenReturn( + TestTopNotificationPanelViewMediator.class.getName()); + + mSystemBarConfigs = new SystemBarConfigs(mResources); + } + + @Test + public void onInit_notificationPanelViewMediatorUsed_topBarNotEnabled_doesNotThrowException() { + when(mResources.getBoolean(R.bool.config_enableTopNavigationBar)).thenReturn(false); + when(mResources.getString(R.string.config_notificationPanelViewMediator)).thenReturn( + NotificationPanelViewMediator.class.getName()); + + mSystemBarConfigs = new SystemBarConfigs(mResources); + } + + @Test public void getTopSystemBarLayoutParams_topBarEnabled_returnsTopSystemBarLayoutParams() { mSystemBarConfigs = new SystemBarConfigs(mResources); WindowManager.LayoutParams lp = mSystemBarConfigs.getLayoutParamsBySide( @@ -239,4 +271,20 @@ public class SystemBarConfigsTest extends SysuiTestCase { when(mResources.getBoolean(R.bool.config_hideRightSystemBarForKeyboard)).thenReturn( false); } + + // Intentionally using a subclass of TopNotificationPanelViewMediator for testing purposes to + // ensure that OEM's will be able to implement and use their own NotificationPanelViewMediator. + private class TestTopNotificationPanelViewMediator extends + TopNotificationPanelViewMediator { + TestTopNotificationPanelViewMediator( + CarNavigationBarController carNavigationBarController, + NotificationPanelViewController notificationPanelViewController, + PowerManagerHelper powerManagerHelper, + BroadcastDispatcher broadcastDispatcher, + CarDeviceProvisionedController carDeviceProvisionedController, + ConfigurationController configurationController) { + super(carNavigationBarController, notificationPanelViewController, powerManagerHelper, + broadcastDispatcher, carDeviceProvisionedController, configurationController); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java index bba69f29a290..aad0d3af6626 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java @@ -35,7 +35,6 @@ import androidx.preference.PreferenceViewHolder; import com.android.settingslib.R; import com.android.settingslib.Utils; import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiEntry.ConnectedInfo; /** * Preference to display a WifiEntry in a wifi picker. @@ -138,11 +137,7 @@ public class WifiEntryPreference extends Preference implements WifiEntry.WifiEnt public void refresh() { setTitle(mWifiEntry.getTitle()); final int level = mWifiEntry.getLevel(); - final ConnectedInfo connectedInfo = mWifiEntry.getConnectedInfo(); - boolean showX = false; - if (connectedInfo != null) { - showX = !connectedInfo.isDefaultNetwork || !connectedInfo.isValidated; - } + final boolean showX = mWifiEntry.shouldShowXLevelIcon(); if (level != mLevel || showX != mShowX) { mLevel = level; mShowX = showX; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java index 40af7dc797b3..c21830b28e3a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java @@ -17,7 +17,6 @@ package com.android.settingslib.wifi; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; @@ -30,7 +29,6 @@ import androidx.preference.PreferenceViewHolder; import com.android.settingslib.R; import com.android.wifitrackerlib.WifiEntry; -import com.android.wifitrackerlib.WifiEntry.ConnectedInfo; import org.junit.Before; import org.junit.Test; @@ -179,43 +177,9 @@ public class WifiEntryPreferenceTest { } @Test - public void levelChanged_notDefaultWifiRefresh_shouldUpdateLevelIcon() { + public void levelChanged_showXWifiRefresh_shouldUpdateLevelIcon() { final List<Drawable> iconList = new ArrayList<>(); - final ConnectedInfo mockConnectedInfo = mock(ConnectedInfo.class); - mockConnectedInfo.isDefaultNetwork = false; - when(mMockWifiEntry.getConnectedInfo()).thenReturn(mockConnectedInfo); - final WifiEntryPreference pref = - new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); - - when(mMockWifiEntry.getLevel()).thenReturn(0); - pref.refresh(); - iconList.add(pref.getIcon()); - when(mMockWifiEntry.getLevel()).thenReturn(1); - pref.refresh(); - iconList.add(pref.getIcon()); - when(mMockWifiEntry.getLevel()).thenReturn(2); - pref.refresh(); - iconList.add(pref.getIcon()); - when(mMockWifiEntry.getLevel()).thenReturn(3); - pref.refresh(); - iconList.add(pref.getIcon()); - when(mMockWifiEntry.getLevel()).thenReturn(4); - pref.refresh(); - iconList.add(pref.getIcon()); - when(mMockWifiEntry.getLevel()).thenReturn(-1); - pref.refresh(); - iconList.add(pref.getIcon()); - - assertThat(iconList).containsExactly(mMockShowXDrawable0, mMockShowXDrawable1, - mMockShowXDrawable2, mMockShowXDrawable3, mMockShowXDrawable4, null); - } - - @Test - public void levelChanged_notValidatedWifiRefresh_shouldUpdateLevelIcon() { - final List<Drawable> iconList = new ArrayList<>(); - final ConnectedInfo mockConnectedInfo = mock(ConnectedInfo.class); - mockConnectedInfo.isValidated = false; - when(mMockWifiEntry.getConnectedInfo()).thenReturn(mockConnectedInfo); + when(mMockWifiEntry.shouldShowXLevelIcon()).thenReturn(true); final WifiEntryPreference pref = new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index dcee2a5b4b20..e24121928808 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -58,6 +58,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private static final int EXPAND_STACK_TO_MENU_DURATION = 250; private static final int LEAVE_PIP_DURATION = 300; private static final int SHIFT_DURATION = 300; + private static final float STASH_RATIO = 0.25f; /** Friction to use for PIP when it moves via physics fling animations. */ private static final float DEFAULT_FRICTION = 2f; @@ -120,6 +121,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; private PhysicsAnimator.FlingConfig mFlingConfigY; + /** FlingConfig instances proviced to PhysicsAnimator for stashing. */ + private PhysicsAnimator.FlingConfig mStashConfigX; /** SpringConfig to use for fling-then-spring animations. */ private final PhysicsAnimator.SpringConfig mSpringConfig = @@ -383,6 +386,21 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, void flingToSnapTarget( float velocityX, float velocityY, @Nullable Runnable updateAction, @Nullable Runnable endAction) { + movetoTarget(velocityX, velocityY, updateAction, endAction, false /* isStash */); + } + + /** + * Stash PiP to the closest edge. + */ + void stashToEdge( + float velocityX, float velocityY, + @Nullable Runnable updateAction, @Nullable Runnable endAction) { + movetoTarget(velocityX, velocityY, updateAction, endAction, true /* isStash */); + } + + private void movetoTarget( + float velocityX, float velocityY, + @Nullable Runnable updateAction, @Nullable Runnable endAction, boolean isStash) { // If we're flinging to a snap target now, we're not springing to catch up to the touch // location now. mSpringingToTouch = false; @@ -391,8 +409,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .flingThenSpring( - FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, - true /* flingMustReachMinOrMax */) + FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX, + mSpringConfig, true /* flingMustReachMinOrMax */) .flingThenSpring( FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig) .withEndActions(endAction); @@ -402,7 +420,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (target, values) -> updateAction.run()); } - final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; + final float offset = ((float) mBounds.width()) * (1.0f - STASH_RATIO); + final float leftEdge = isStash ? mMovementBounds.left - offset : mMovementBounds.left; + final float rightEdge = isStash ? mMovementBounds.right + offset : mMovementBounds.right; + + final float xEndValue = velocityX < 0 ? leftEdge : rightEdge; final float estimatedFlingYEndValue = PhysicsAnimator.estimateFlingEndValue( mTemporaryBounds.top, velocityY, mFlingConfigY); @@ -506,6 +528,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, DEFAULT_FRICTION, mMovementBounds.left, mMovementBounds.right); mFlingConfigY = new PhysicsAnimator.FlingConfig( DEFAULT_FRICTION, mMovementBounds.top, mMovementBounds.bottom); + final float offset = ((float) mBounds.width()) * (1.0f - STASH_RATIO); + mStashConfigX = new PhysicsAnimator.FlingConfig( + DEFAULT_FRICTION, mMovementBounds.left - offset, mMovementBounds.right + offset); } /** diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 97b3484292c6..858683c4e2d4 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -16,6 +16,7 @@ package com.android.systemui.pip.phone; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; @@ -33,6 +34,7 @@ import android.graphics.Rect; import android.graphics.drawable.TransitionDrawable; import android.os.Handler; import android.os.RemoteException; +import android.provider.DeviceConfig; import android.util.Log; import android.util.Size; import android.view.Gravity; @@ -102,6 +104,13 @@ public class PipTouchHandler { private boolean mShowPipMenuOnAnimationEnd = false; /** + * Whether PIP stash is enabled or not. When enabled, if at the time of fling-release the + * PIP bounds is outside the left/right edge of the screen, it will be shown in "stashed" mode, + * where PIP will only show partially. + */ + private boolean mEnableStash = false; + + /** * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move * PIP. */ @@ -306,6 +315,22 @@ public class PipTouchHandler { }); mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); + + mEnableStash = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + PIP_STASHING, + /* defaultValue = */ false); + deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + context.getMainExecutor(), + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(PIP_STASHING)) { + mEnableStash = properties.getBoolean( + PIP_STASHING, /* defaultValue = */ false); + } + } + }); } private void reloadResources() { @@ -986,9 +1011,20 @@ public class PipTouchHandler { // Reset the touch state on up before the fling settles mTouchState.reset(); - mMotionHelper.flingToSnapTarget(vel.x, vel.y, - PipTouchHandler.this::updateDismissFraction /* updateAction */, - this::flingEndAction /* endAction */); + final Rect animatingBounds = mMotionHelper.getPossiblyAnimatingBounds(); + // If User releases the PIP window while it's out of the display bounds, put + // PIP into stashed mode. + if (mEnableStash + && (animatingBounds.right > mPipBoundsHandler.getDisplayBounds().right + || animatingBounds.left < mPipBoundsHandler.getDisplayBounds().left)) { + mMotionHelper.stashToEdge(vel.x, vel.y, + PipTouchHandler.this::updateDismissFraction /* updateAction */, + this::flingEndAction /* endAction */); + } else { + mMotionHelper.flingToSnapTarget(vel.x, vel.y, + PipTouchHandler.this::updateDismissFraction /* updateAction */, + this::flingEndAction /* endAction */); + } } else if (mTouchState.isDoubleTap()) { // Expand to fullscreen if this is a double tap // the PiP should be frozen until the transition ends diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 82c1f243dcdb..fd0476b76a9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -110,4 +110,11 @@ public abstract class ListEntry { mPreviousAttachState.clone(mAttachState); mAttachState.reset(); } + + /** + * True if this entry was attached in the last pass, else false. + */ + public boolean wasAttachedInPreviousPass() { + return getPreviousAttachState().getParent() != null; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index df63fec62e4e..a0ef1b674af3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -513,26 +513,64 @@ public class ShadeListBuilder implements Dumpable { } } - private void stabilizeGroupingNotifs(List<ListEntry> list) { + private void stabilizeGroupingNotifs(List<ListEntry> topLevelList) { if (mNotifStabilityManager == null) { return; } - for (int i = 0; i < list.size(); i++) { - final ListEntry tle = list.get(i); - if (tle.getPreviousAttachState().getParent() == null) { - continue; // new entries are allowed + for (int i = 0; i < topLevelList.size(); i++) { + final ListEntry tle = topLevelList.get(i); + if (tle instanceof GroupEntry) { + // maybe put children back into their old group (including moving back to top-level) + GroupEntry groupEntry = (GroupEntry) tle; + List<NotificationEntry> children = groupEntry.getRawChildren(); + for (int j = 0; j < groupEntry.getChildren().size(); j++) { + if (maybeSuppressGroupChange(children.get(j), topLevelList)) { + // child was put back into its previous group, so we remove it from this + // group + children.remove(j); + j--; + } + } + } else { + // maybe put top-level-entries back into their previous groups + if (maybeSuppressGroupChange(tle.getRepresentativeEntry(), topLevelList)) { + // entry was put back into its previous group, so we remove it from the list of + // top-level-entries + topLevelList.remove(i); + i--; + } } + } + } - final GroupEntry prevParent = tle.getPreviousAttachState().getParent(); - final GroupEntry assignedParent = tle.getParent(); - if (prevParent != assignedParent) { - if (!mNotifStabilityManager.isGroupChangeAllowed(tle.getRepresentativeEntry())) { - tle.getAttachState().getSuppressedChanges().setParent(assignedParent); - tle.setParent(prevParent); + /** + * Returns true if the group change was suppressed, else false + */ + private boolean maybeSuppressGroupChange(NotificationEntry entry, List<ListEntry> out) { + if (!entry.wasAttachedInPreviousPass()) { + return false; // new entries are allowed + } + + final GroupEntry prevParent = entry.getPreviousAttachState().getParent(); + final GroupEntry assignedParent = entry.getParent(); + if (prevParent != assignedParent + && !mNotifStabilityManager.isGroupChangeAllowed(entry.getRepresentativeEntry())) { + entry.getAttachState().getSuppressedChanges().setParent(assignedParent); + entry.setParent(prevParent); + if (prevParent == ROOT_ENTRY) { + out.add(entry); + } else if (prevParent != null) { + prevParent.addChild(entry); + if (!mGroups.containsKey(prevParent.getKey())) { + mGroups.put(prevParent.getKey(), prevParent); } } + + return true; } + + return false; } private void promoteNotifs(List<ListEntry> list) { @@ -577,6 +615,17 @@ public class ShadeListBuilder implements Dumpable { } else if (group.getSummary() == null || children.size() < MIN_CHILDREN_FOR_GROUP) { + + if (group.getSummary() != null + && group.wasAttachedInPreviousPass() + && mNotifStabilityManager != null + && !mNotifStabilityManager.isGroupChangeAllowed(group.getSummary())) { + // if this group was previously attached and group changes aren't + // allowed, keep it around until group changes are allowed again + group.getAttachState().getSuppressedChanges().setWasPruneSuppressed(true); + continue; + } + // If the group doesn't provide a summary or is too small, ignore it and add // its children (if any) directly to top-level. @@ -718,6 +767,12 @@ public class ShadeListBuilder implements Dumpable { curr.getParent()); } + if (curr.getSuppressedChanges().getWasPruneSuppressed()) { + mLogger.logGroupPruningSuppressed( + mIterationCount, + curr.getParent()); + } + if (curr.getExcludingFilter() != prev.getExcludingFilter()) { mLogger.logFilterChanged( mIterationCount, @@ -867,9 +922,9 @@ public class ShadeListBuilder implements Dumpable { NotifSection finalSection = newSection; - // are we changing sections of this entry? + // have we seen this entry before and are we changing its section? if (mNotifStabilityManager != null - && prevAttachState.getParent() != null + && entry.wasAttachedInPreviousPass() && newSection != prevAttachState.getSection()) { // are section changes allowed? diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt index 3eb2e610f329..584563b6c388 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt @@ -24,28 +24,37 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.NotifS */ data class SuppressedAttachState private constructor( /** - * Null if not attached to the current shade list. If top-level, then the shade list root. If - * part of a group, then that group's GroupEntry. + * The suppressed section assignment for this ListEntry. + * Null if no section change was suppressed. + */ + var section: NotifSection?, + + /** + * The suppressed parent assignment for this ListEntry. + * - Null if no parent change was suppressed. + * - Root if suppressing group change to top-level + * - GroupEntry if suppressing group change to a different group */ var parent: GroupEntry?, /** - * The assigned section for this ListEntry. If the child of the group, this will be the - * parent's section. Null if not attached to the list. + * Whether the ListEntry would have been pruned had its group change not been suppressed. */ - var section: NotifSection? + var wasPruneSuppressed: Boolean ) { /** Copies the state of another instance. */ fun clone(other: SuppressedAttachState) { parent = other.parent section = other.section + wasPruneSuppressed = other.wasPruneSuppressed } /** Resets back to a "clean" state (the same as created by the factory method) */ fun reset() { parent = null section = null + wasPruneSuppressed = false } companion object { @@ -53,7 +62,8 @@ data class SuppressedAttachState private constructor( fun create(): SuppressedAttachState { return SuppressedAttachState( null, - null) + null, + false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 08030f8201a2..5d6c0437ce9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -110,7 +110,7 @@ public class VisualStabilityCoordinator implements Coordinator { public boolean isGroupChangeAllowed(NotificationEntry entry) { final boolean isGroupChangeAllowedForEntry = mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey()); - mIsSuppressingGroupChange |= isGroupChangeAllowedForEntry; + mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry; return isGroupChangeAllowedForEntry; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index 9ee7db738c20..5a35127397b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -174,8 +174,19 @@ class ShadeListBuilderLogger @Inject constructor( str1 = suppressedParent?.key str2 = keepingParent?.key }, { - "(Build $long1) Change of parent to '$str1' suppressed; " + - "keeping parent '$str2'" + "(Build $long1) Change of parent to '$str1' suppressed; keeping parent '$str2'" + }) + } + + fun logGroupPruningSuppressed( + buildId: Int, + keepingParent: GroupEntry? + ) { + buffer.log(TAG, INFO, { + int1 = buildId + str1 = keepingParent?.key + }, { + "(Build $long1) Group pruning suppressed; keeping parent '$str1'" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java index c1f468a3072f..e784ec62c8e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java @@ -56,7 +56,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { return false; } - return entry.getParent().getChildren().size() == 1; + return !isGroupSummary(entry) && entry.getParent().getChildren().size() == 1; } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index d8eecef024ce..3aba7ca1ad7c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -76,6 +76,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; @@ -829,7 +830,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } class C implements Callbacks { - private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>(); + private final Map<Callbacks, Handler> mCallbackMap = new ConcurrentHashMap<>(); public void add(Callbacks callback, Handler handler) { if (callback == null || handler == null) throw new IllegalArgumentException(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 11920233e403..d93cc05c57db 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -182,7 +182,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // IBiometricsFace@1.0 does not support detection, only authentication. when(mFaceSensorProperties.isEmpty()).thenReturn(false); when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorProperties(0 /* id */, - false /* supportsFaceDetection */, true /* supportsSelfIllumination */)); + false /* supportsFaceDetection */, true /* supportsSelfIllumination */, + 1 /* maxTemplatesAllowed */)); when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 8acb705c744d..2ce22a6f71c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.util.time.FakeSystemClock; @@ -964,6 +965,198 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void testStabilizeGroupsDoesNotAllowGrouping() { + // GIVEN one group child without a summary yet + addGroupChild(0, PACKAGE_1, GROUP_1); + + dispatchBuild(); + + // GIVEN visual stability manager doesn't allow any group changes + mListBuilder.setNotifStabilityManager( + new TestableStabilityManager().setAllowGroupChanges(false)); + + // WHEN we run the pipeline with the addition of a group summary & child + addGroupSummary(1, PACKAGE_1, GROUP_1); + addGroupChild(2, PACKAGE_1, GROUP_1); + + dispatchBuild(); + + // THEN all notifications are top-level and the summary doesn't show yet + // because group changes aren't allowed by the stability manager + verifyBuiltList( + notif(0), + notif(2) + ); + } + + @Test + public void testStabilizeGroupsAllowsGroupingAllNewNotifications() { + // GIVEN visual stability manager doesn't allow any group changes + mListBuilder.setNotifStabilityManager( + new TestableStabilityManager().setAllowGroupChanges(false)); + + // WHEN we run the pipeline with all new notification groups + addGroupChild(0, PACKAGE_1, GROUP_1); + addGroupSummary(1, PACKAGE_1, GROUP_1); + addGroupChild(2, PACKAGE_1, GROUP_1); + addGroupSummary(3, PACKAGE_2, GROUP_2); + addGroupChild(4, PACKAGE_2, GROUP_2); + addGroupChild(5, PACKAGE_2, GROUP_2); + + dispatchBuild(); + + // THEN all notifications are grouped since they're all new + verifyBuiltList( + group( + summary(1), + child(0), + child(2) + ), + group( + summary(3), + child(4), + child(5) + ) + ); + } + + + @Test + public void testStabilizeGroupsAllowsGroupingOnlyNewNotifications() { + // GIVEN one group child without a summary yet + addGroupChild(0, PACKAGE_1, GROUP_1); + + dispatchBuild(); + + // GIVEN visual stability manager doesn't allow any group changes + mListBuilder.setNotifStabilityManager( + new TestableStabilityManager().setAllowGroupChanges(false)); + + // WHEN we run the pipeline with the addition of a group summary & child + addGroupSummary(1, PACKAGE_1, GROUP_1); + addGroupChild(2, PACKAGE_1, GROUP_1); + addGroupSummary(3, PACKAGE_2, GROUP_2); + addGroupChild(4, PACKAGE_2, GROUP_2); + addGroupChild(5, PACKAGE_2, GROUP_2); + + dispatchBuild(); + + // THEN all notifications are top-level and the summary doesn't show yet + // because group changes aren't allowed by the stability manager + verifyBuiltList( + notif(0), + group( + summary(3), + child(4), + child(5) + ), + notif(2) + ); + } + + @Test + public void testStabilizeGroupsHidesGroupSummary() { + // GIVEN one group child with a summary + addGroupChild(0, PACKAGE_1, GROUP_1); + addGroupSummary(1, PACKAGE_1, GROUP_1); + + dispatchBuild(); // group summary is hidden because it needs at least 2 children to group + + // GIVEN visual stability manager doesn't allow any group changes + mListBuilder.setNotifStabilityManager( + new TestableStabilityManager().setAllowGroupChanges(false)); + + // WHEN we run the pipeline with the addition of a child + addGroupChild(2, PACKAGE_1, GROUP_1); + + dispatchBuild(); + + // THEN the children notifications are top-level and the summary still doesn't show yet + // because group changes aren't allowed by the stability manager + verifyBuiltList( + notif(0), + notif(2) + ); + } + + @Test + public void testStabilizeGroupsDelayedSummaryRendersAllNotifsTopLevel() { + // GIVEN group children posted without a summary + addGroupChild(0, PACKAGE_1, GROUP_1); + addGroupChild(1, PACKAGE_1, GROUP_1); + addGroupChild(2, PACKAGE_1, GROUP_1); + addGroupChild(3, PACKAGE_1, GROUP_1); + + dispatchBuild(); + + // GIVEN visual stability manager doesn't allow any group changes + final TestableStabilityManager stabilityManager = + new TestableStabilityManager().setAllowGroupChanges(false); + mListBuilder.setNotifStabilityManager(stabilityManager); + + // WHEN the delayed summary is posted + addGroupSummary(4, PACKAGE_1, GROUP_1); + + dispatchBuild(); + + // THEN all entries are top-level since group changes aren't allowed + verifyBuiltList( + notif(0), + notif(1), + notif(2), + notif(3), + notif(4) + ); + + // WHEN visual stability manager allows group changes again + stabilityManager.setAllowGroupChanges(true); + stabilityManager.invalidateList(); + + // THEN entries are grouped + verifyBuiltList( + group( + summary(4), + child(0), + child(1), + child(2), + child(3) + ) + ); + } + + @Test + public void testStabilizeSectionDisallowsNewSection() { + // GIVEN one non-default sections + final NotifSectioner originalSectioner = new PackageSectioner(PACKAGE_1); + mListBuilder.setSectioners(List.of(originalSectioner)); + + // GIVEN notifications that's sectioned by sectioner1 + addNotif(0, PACKAGE_1); + dispatchBuild(); + assertEquals(originalSectioner, mEntrySet.get(0).getSection().getSectioner()); + + // WHEN section changes aren't allowed + final TestableStabilityManager stabilityManager = + new TestableStabilityManager().setAllowSectionChanges(false); + mListBuilder.setNotifStabilityManager(stabilityManager); + + // WHEN we try to change the section + final NotifSectioner newSectioner = new PackageSectioner(PACKAGE_1); + mListBuilder.setSectioners(List.of(newSectioner, originalSectioner)); + dispatchBuild(); + + // THEN the section remains the same since section changes aren't allowed + assertEquals(originalSectioner, mEntrySet.get(0).getSection().getSectioner()); + + // WHEN section changes are allowed again + stabilityManager.setAllowSectionChanges(true); + stabilityManager.invalidateList(); + + // THEN the section updates + assertEquals(newSectioner, mEntrySet.get(0).getSection().getSectioner()); + } + + @Test public void testDispatchListOnBeforeSort() { // GIVEN a registered OnBeforeSortListener RecordingOnBeforeSortListener listener = @@ -999,8 +1192,8 @@ public class ShadeListBuilderTest extends SysuiTestCase { @Test public void testDispatchListOnBeforeRender() { // GIVEN a registered OnBeforeRenderList - RecordingOnBeforeRenderistener listener = - new RecordingOnBeforeRenderistener(); + RecordingOnBeforeRenderListener listener = + new RecordingOnBeforeRenderListener(); mListBuilder.addOnBeforeRenderListListener(listener); // GIVEN some new notifs out of order @@ -1450,7 +1643,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { } } - private static class RecordingOnBeforeRenderistener + private static class RecordingOnBeforeRenderListener implements OnBeforeRenderListListener { List<ListEntry> mEntriesReceived; @@ -1460,6 +1653,39 @@ public class ShadeListBuilderTest extends SysuiTestCase { } } + private static class TestableStabilityManager extends NotifStabilityManager { + boolean mAllowGroupChanges = true; + boolean mAllowSectionChanges = true; + + TestableStabilityManager() { + super("Test"); + } + + TestableStabilityManager setAllowGroupChanges(boolean allowGroupChanges) { + mAllowGroupChanges = allowGroupChanges; + return this; + } + + TestableStabilityManager setAllowSectionChanges(boolean allowSectionChanges) { + mAllowSectionChanges = allowSectionChanges; + return this; + } + + @Override + public void onBeginRun() { + } + + @Override + public boolean isGroupChangeAllowed(NotificationEntry entry) { + return mAllowGroupChanges; + } + + @Override + public boolean isSectionChangeAllowed(NotificationEntry entry) { + return mAllowSectionChanges; + } + } + private static final String PACKAGE_1 = "com.test1"; private static final String PACKAGE_2 = "com.test2"; private static final String PACKAGE_3 = "org.test3"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 605b4d18d2f1..4edca7dd43d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -296,6 +296,22 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } @Test + public void testNotSuppressingGroupChangesAnymore_invalidationCalled() { + // GIVEN visual stability is being maintained b/c panel is expanded + setPulsing(false); + setScreenOn(true); + setPanelExpanded(true); + + assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + + // WHEN the panel isn't expanded anymore + setPanelExpanded(false); + + // invalidate is called because we were previously suppressing a group change + verifyInvalidateCalled(true); + } + + @Test public void testHeadsUp_allowedToChangeGroupAndSection() { // GIVEN group + section changes disallowed setScreenOn(true); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7e6649660aa9..b55d55562110 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -97,7 +97,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; @@ -110,7 +109,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; @@ -135,7 +133,6 @@ import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToS import android.Manifest; import android.Manifest.permission; -import android.annotation.BroadcastBehavior; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -265,7 +262,6 @@ import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.permission.PermissionManagerInternal.CheckPermissionDelegate; import android.provider.DeviceConfig; -import android.provider.DeviceConfig.Properties; import android.provider.Settings; import android.server.ServerProtoEnums; import android.sysprop.InitProperties; @@ -392,11 +388,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; public class ActivityManagerService extends IActivityManager.Stub @@ -422,7 +414,6 @@ public class ActivityManagerService extends IActivityManager.Stub private static final String TAG_POWER = TAG + POSTFIX_POWER; static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS; static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES; - static final String TAG_PSS = TAG + POSTFIX_PSS; private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE; private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; static final String TAG_UID_OBSERVERS = TAG + POSTFIX_UID_OBSERVERS; @@ -436,21 +427,9 @@ public class ActivityManagerService extends IActivityManager.Stub "com.android.internal.intent.action.BUGREPORT_REQUESTED"; private static final String SHELL_APP_PACKAGE = "com.android.shell"; - /** Control over CPU and battery monitoring */ - // write battery stats every 30 minutes. - static final long BATTERY_STATS_TIME = 30 * 60 * 1000; - static final boolean MONITOR_CPU_USAGE = true; - // don't sample cpu less than every 5 seconds. - static final long MONITOR_CPU_MIN_TIME = 5 * 1000; - // wait possibly forever for next cpu sample. - static final long MONITOR_CPU_MAX_TIME = 0x0fffffff; - static final boolean MONITOR_THREAD_CPU_USAGE = false; - // The flags that are set for all calls we make to the package manager. public static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES; - static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; - static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed"; public static final String ANR_TRACE_DIR = "/data/anr"; @@ -493,37 +472,6 @@ public class ActivityManagerService extends IActivityManager.Stub private static final String INTENT_REMOTE_BUGREPORT_FINISHED = "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED"; - /** - * Broadcast sent when heap dump collection has been completed. - */ - @BroadcastBehavior(includeBackground = true, protectedBroadcast = true) - private static final String ACTION_HEAP_DUMP_FINISHED = - "com.android.internal.intent.action.HEAP_DUMP_FINISHED"; - - /** - * The process we are reporting - */ - private static final String EXTRA_HEAP_DUMP_PROCESS_NAME = - "com.android.internal.extra.heap_dump.PROCESS_NAME"; - - /** - * The size limit the process reached. - */ - private static final String EXTRA_HEAP_DUMP_SIZE_BYTES = - "com.android.internal.extra.heap_dump.SIZE_BYTES"; - - /** - * Whether the user initiated the dump or not. - */ - private static final String EXTRA_HEAP_DUMP_IS_USER_INITIATED = - "com.android.internal.extra.heap_dump.IS_USER_INITIATED"; - - /** - * Optional name of package to directly launch. - */ - private static final String EXTRA_HEAP_DUMP_REPORT_PACKAGE = - "com.android.internal.extra.heap_dump.REPORT_PACKAGE"; - // If set, we will push process association information in to procstats. static final boolean TRACK_PROCSTATS_ASSOCIATIONS = true; @@ -556,7 +504,6 @@ public class ActivityManagerService extends IActivityManager.Stub private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes. OomAdjuster mOomAdjuster; - final LowMemDetector mLowMemDetector; static final String EXTRA_TITLE = "android.intent.extra.TITLE"; static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION"; @@ -918,28 +865,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ final ArrayList<ProcessRecord> mProcessesToGc = new ArrayList<ProcessRecord>(); - /** - * Processes we want to collect PSS data from. - */ - final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>(); - - /** - * Depth of overlapping activity-start PSS deferral notes - */ - private final AtomicInteger mActivityStartingNesting = new AtomicInteger(0); - private final ActivityMetricsLaunchObserver mActivityLaunchObserver = new ActivityMetricsLaunchObserver() { @Override public void onActivityLaunched(byte[] activity, int temperature) { - // This is safe to force to the head of the queue because it relies only - // on refcounting to track begin/end of deferrals, not on actual - // message ordering. We don't care *what* activity is being - // launched; only that we're doing so. - if (mPssDeferralTime > 0) { - final Message msg = mBgHandler.obtainMessage(DEFER_PSS_MSG); - mBgHandler.sendMessageAtFrontOfQueue(msg); - } + mAppProfiler.onActivityLaunched(); } // The other observer methods are unused @@ -964,45 +894,9 @@ public class ActivityManagerService extends IActivityManager.Stub } }; - /** - * How long we defer PSS gathering while activities are starting, in milliseconds. - * This is adjustable via DeviceConfig. If it is zero or negative, no PSS deferral - * is done. - */ - private volatile long mPssDeferralTime = 0; - private static final String ACTIVITY_START_PSS_DEFER_CONFIG = "activity_start_pss_defer"; - private boolean mBinderTransactionTrackingEnabled = false; /** - * Last time we requested PSS data of all processes. - */ - long mLastFullPssTime = SystemClock.uptimeMillis(); - - /** - * If set, the next time we collect PSS data we should do a full collection - * with data from native processes and the kernel. - */ - boolean mFullPssPending = false; - - /** - * Observe DeviceConfig changes to the PSS calculation interval - */ - private final DeviceConfig.OnPropertiesChangedListener mPssDelayConfigListener = - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(Properties properties) { - if (properties.getKeyset().contains(ACTIVITY_START_PSS_DEFER_CONFIG)) { - mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0); - if (DEBUG_PSS) { - Slog.d(TAG_PSS, "Activity-start PSS delay now " - + mPssDeferralTime + " ms"); - } - } - } - }; - - /** * Fingerprints (hashCode()) of stack traces that we've * already logged DropBox entries for. Guarded by itself. If * something (rogue user app) forces this over @@ -1242,41 +1136,11 @@ public class ActivityManagerService extends IActivityManager.Stub int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; /** - * Allow the current computed overall memory level of the system to go down? - * This is set to false when we are killing processes for reasons other than - * memory management, so that the now smaller process list will not be taken as - * an indication that memory is tighter. - */ - boolean mAllowLowerMemLevel = false; - - /** - * The last computed memory level, for holding when we are in a state that - * processes are going away for other reasons. - */ - int mLastMemoryLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; - - /** - * The last total number of process we have, to determine if changes actually look - * like a shrinking number of process due to lower RAM. - */ - int mLastNumProcesses; - - /** * The uptime of the last time we performed idle maintenance. */ long mLastIdleTime = SystemClock.uptimeMillis(); /** - * Total time spent with RAM that has been added in the past since the last idle time. - */ - long mLowRamTimeSinceLastIdle = 0; - - /** - * If RAM is currently low, when that horrible situation started. - */ - long mLowRamStartTime = 0; - - /** * For reporting to battery stats the current top application. */ private String mCurResumedPackage = null; @@ -1295,13 +1159,6 @@ public class ActivityManagerService extends IActivityManager.Stub */ boolean mSafeMode; - /** - * If true, we are running under a test environment so will sample PSS from processes - * much more rapidly to try to collect better data when the tests are rapidly - * running through apps. - */ - boolean mTestPssMode = false; - String mDebugApp = null; boolean mWaitForDebugger = false; boolean mDebugTransient = false; @@ -1309,61 +1166,6 @@ public class ActivityManagerService extends IActivityManager.Stub boolean mOrigWaitForDebugger = false; boolean mAlwaysFinishActivities = false; - class ProfileData { - private String mProfileApp = null; - private ProcessRecord mProfileProc = null; - private ProfilerInfo mProfilerInfo = null; - - void setProfileApp(String profileApp) { - mProfileApp = profileApp; - if (mAtmInternal != null) { - mAtmInternal.setProfileApp(profileApp); - } - } - - String getProfileApp() { - return mProfileApp; - } - - void setProfileProc(ProcessRecord profileProc) { - mProfileProc = profileProc; - if (mAtmInternal != null) { - mAtmInternal.setProfileProc(profileProc == null ? null - : profileProc.getWindowProcessController()); - } - } - - ProcessRecord getProfileProc() { - return mProfileProc; - } - - void setProfilerInfo(ProfilerInfo profilerInfo) { - mProfilerInfo = profilerInfo; - if (mAtmInternal != null) { - mAtmInternal.setProfilerInfo(profilerInfo); - } - } - - ProfilerInfo getProfilerInfo() { - return mProfilerInfo; - } - } - final ProfileData mProfileData = new ProfileData(); - - /** - * Stores a map of process name -> agent string. When a process is started and mAgentAppMap - * is not null, this map is checked and the mapped agent installed during bind-time. Note: - * A non-null agent in mProfileInfo overrides this. - */ - private @Nullable Map<String, String> mAppAgentMap = null; - - int mProfileType = 0; - final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>(); - String mMemWatchDumpProcName; - Uri mMemWatchDumpUri; - int mMemWatchDumpPid; - int mMemWatchDumpUid; - private boolean mMemWatchIsUserInitiated; String mTrackAllocationApp = null; String mNativeDebuggingApp = null; @@ -1402,27 +1204,6 @@ public class ActivityManagerService extends IActivityManager.Stub final AnrHelper mAnrHelper = new AnrHelper(this); - /** - * Runtime CPU use collection thread. This object's lock is used to - * perform synchronization with the thread (notifying it to run). - */ - final Thread mProcessCpuThread; - - /** - * Used to collect per-process CPU use for ANRs, battery stats, etc. - * Must acquire this object's lock when accessing it. - * NOTE: this lock will be held while doing long operations (trawling - * through all processes in /proc), so it should never be acquired by - * any critical paths such as when holding the main activity manager lock. - */ - final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker( - MONITOR_THREAD_CPU_USAGE); - final AtomicLong mLastCpuTime = new AtomicLong(0); - final AtomicBoolean mProcessCpuMutexFree = new AtomicBoolean(true); - final CountDownLatch mProcessCpuInitLatch = new CountDownLatch(1); - - long mLastWriteTime = 0; - /** Set to true after the system has finished booting. */ volatile boolean mBooted = false; @@ -1561,6 +1342,23 @@ public class ActivityManagerService extends IActivityManager.Stub */ private long mLastBinderHeavyHitterAutoSamplerStart = 0L; + final AppProfiler mAppProfiler; + + private static final int INDEX_NATIVE_PSS = 0; + private static final int INDEX_NATIVE_SWAP_PSS = 1; + private static final int INDEX_NATIVE_RSS = 2; + private static final int INDEX_DALVIK_PSS = 3; + private static final int INDEX_DALVIK_SWAP_PSS = 4; + private static final int INDEX_DALVIK_RSS = 5; + private static final int INDEX_OTHER_PSS = 6; + private static final int INDEX_OTHER_SWAP_PSS = 7; + private static final int INDEX_OTHER_RSS = 8; + private static final int INDEX_TOTAL_PSS = 9; + private static final int INDEX_TOTAL_SWAP_PSS = 10; + private static final int INDEX_TOTAL_RSS = 11; + private static final int INDEX_TOTAL_NATIVE_PSS = 12; + private static final int INDEX_LAST = 13; + final class UiHandler extends Handler { public UiHandler() { super(com.android.server.UiThread.get().getLooper(), null, true); @@ -1752,63 +1550,10 @@ public class ActivityManagerService extends IActivityManager.Stub break; } case POST_DUMP_HEAP_NOTIFICATION_MSG: { - final String procName; - final int uid; - final long memLimit; - final String reportPackage; - final boolean isUserInitiated; - synchronized (ActivityManagerService.this) { - uid = mMemWatchDumpUid; - procName = mMemWatchDumpProcName; - Pair<Long, String> val = mMemWatchProcesses.get(procName, uid); - if (val == null) { - val = mMemWatchProcesses.get(procName, 0); - } - if (val != null) { - memLimit = val.first; - reportPackage = val.second; - } else { - memLimit = 0; - reportPackage = null; - } - isUserInitiated = mMemWatchIsUserInitiated; - - mMemWatchDumpUri = null; - mMemWatchDumpProcName = null; - mMemWatchDumpPid = -1; - mMemWatchDumpUid = -1; - } - if (procName == null) { - return; - } - - if (DEBUG_PSS) Slog.d(TAG_PSS, - "Showing dump heap notification from " + procName + "/" + uid); - - Intent dumpFinishedIntent = new Intent(ACTION_HEAP_DUMP_FINISHED); - // Send this only to the Shell package. - dumpFinishedIntent.setPackage("com.android.shell"); - dumpFinishedIntent.putExtra(Intent.EXTRA_UID, uid); - dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_IS_USER_INITIATED, isUserInitiated); - dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_SIZE_BYTES, memLimit); - dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_REPORT_PACKAGE, reportPackage); - dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_PROCESS_NAME, procName); - - mContext.sendBroadcastAsUser(dumpFinishedIntent, - UserHandle.getUserHandleForUid(uid)); + mAppProfiler.handlePostDumpHeapNotification(); } break; case ABORT_DUMPHEAP_MSG: { - String procName = (String) msg.obj; - if (procName != null) { - synchronized (ActivityManagerService.this) { - if (procName.equals(mMemWatchDumpProcName)) { - mMemWatchDumpProcName = null; - mMemWatchDumpUri = null; - mMemWatchDumpPid = -1; - mMemWatchDumpUid = -1; - } - } - } + mAppProfiler.handleAbortDumpHeap((String) msg.obj); } break; case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: { IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj; @@ -1841,147 +1586,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - static final int COLLECT_PSS_BG_MSG = 1; - static final int DEFER_PSS_MSG = 2; - static final int STOP_DEFERRING_PSS_MSG = 3; - - final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case COLLECT_PSS_BG_MSG: { - long start = SystemClock.uptimeMillis(); - MemInfoReader memInfo = null; - synchronized (ActivityManagerService.this) { - if (mFullPssPending) { - mFullPssPending = false; - memInfo = new MemInfoReader(); - } - } - if (memInfo != null) { - updateCpuStatsNow(); - long nativeTotalPss = 0; - final List<ProcessCpuTracker.Stats> stats; - synchronized (mProcessCpuTracker) { - stats = mProcessCpuTracker.getStats( (st)-> { - return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID; - }); - } - final int N = stats.size(); - for (int j = 0; j < N; j++) { - synchronized (mPidsSelfLocked) { - if (mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) { - // This is one of our own processes; skip it. - continue; - } - } - nativeTotalPss += Debug.getPss(stats.get(j).pid, null, null); - } - memInfo.readMemInfo(); - synchronized (mProcessStats.mLock) { - if (DEBUG_PSS) Slog.d(TAG_PSS, "Collected native and kernel memory in " - + (SystemClock.uptimeMillis()-start) + "ms"); - final long cachedKb = memInfo.getCachedSizeKb(); - final long freeKb = memInfo.getFreeSizeKb(); - final long zramKb = memInfo.getZramTotalSizeKb(); - final long kernelKb = memInfo.getKernelUsedSizeKb(); - EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, - kernelKb*1024, nativeTotalPss*1024); - mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, - nativeTotalPss); - } - } - - int num = 0; - long[] tmp = new long[3]; - do { - ProcessRecord proc; - int procState; - int statType; - int pid = -1; - long lastPssTime; - synchronized (ActivityManagerService.this) { - if (mPendingPssProcesses.size() <= 0) { - if (mTestPssMode || DEBUG_PSS) Slog.d(TAG_PSS, - "Collected pss of " + num + " processes in " - + (SystemClock.uptimeMillis() - start) + "ms"); - mPendingPssProcesses.clear(); - return; - } - proc = mPendingPssProcesses.remove(0); - procState = proc.pssProcState; - statType = proc.pssStatType; - lastPssTime = proc.lastPssTime; - long now = SystemClock.uptimeMillis(); - if (proc.thread != null && procState == proc.setProcState - && (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) - < now) { - pid = proc.pid; - } else { - ProcessList.abortNextPssTime(proc.procStateMemTracker); - if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid + - ": still need " + - (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE-now) + - "ms until safe"); - proc = null; - pid = 0; - } - } - if (proc != null) { - long startTime = SystemClock.currentThreadTimeMillis(); - // skip background PSS calculation of apps that are capturing - // camera imagery - final boolean usingCamera = isCameraActiveForUid(proc.uid); - long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null); - long endTime = SystemClock.currentThreadTimeMillis(); - synchronized (ActivityManagerService.this) { - if (pss != 0 && proc.thread != null && proc.setProcState == procState - && proc.pid == pid && proc.lastPssTime == lastPssTime) { - num++; - ProcessList.commitNextPssTime(proc.procStateMemTracker); - recordPssSampleLocked(proc, procState, pss, tmp[0], tmp[1], tmp[2], - statType, endTime-startTime, SystemClock.uptimeMillis()); - } else { - ProcessList.abortNextPssTime(proc.procStateMemTracker); - if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid + - ": " + (proc.thread == null ? "NO_THREAD " : "") + - (usingCamera ? "CAMERA " : "") + - (proc.pid != pid ? "PID_CHANGED " : "") + - " initState=" + procState + " curState=" + - proc.setProcState + " " + - (proc.lastPssTime != lastPssTime ? "TIME_CHANGED" : "")); - } - } - } - } while (true); - } - - case DEFER_PSS_MSG: { - deferPssForActivityStart(); - } break; - - case STOP_DEFERRING_PSS_MSG: { - final int nesting = mActivityStartingNesting.decrementAndGet(); - if (nesting <= 0) { - if (DEBUG_PSS) { - Slog.d(TAG_PSS, "PSS activity start deferral interval ended; now " - + nesting); - } - if (nesting < 0) { - Slog.wtf(TAG, "Activity start nesting undercount!"); - mActivityStartingNesting.incrementAndGet(); - } - } else { - if (DEBUG_PSS) { - Slog.d(TAG_PSS, "Still deferring PSS, nesting=" + nesting); - } - } - } - break; - - } - } - }; public void setSystemProcess() { try { @@ -1992,10 +1596,7 @@ public class ActivityManagerService extends IActivityManager.Stub DUMP_FLAG_PRIORITY_HIGH); ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); ServiceManager.addService("dbinfo", new DbBinder(this)); - if (MONITOR_CPU_USAGE) { - ServiceManager.addService("cpuinfo", new CpuBinder(this), - /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); - } + mAppProfiler.setCpuInfoService(); ServiceManager.addService("permission", new PermissionController(this)); ServiceManager.addService("processinfo", new ProcessInfoService(this)); ServiceManager.addService("cacheinfo", new CacheBinder(this)); @@ -2152,37 +1753,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - static class CpuBinder extends Binder { - ActivityManagerService mActivityManagerService; - private final PriorityDump.PriorityDumper mPriorityDumper = - new PriorityDump.PriorityDumper() { - @Override - public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, - boolean asProto) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext, - "cpuinfo", pw)) return; - synchronized (mActivityManagerService.mProcessCpuTracker) { - if (asProto) { - mActivityManagerService.mProcessCpuTracker.dumpProto(fd); - return; - } - pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad()); - pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState( - SystemClock.uptimeMillis())); - } - } - }; - - CpuBinder(ActivityManagerService activityManagerService) { - mActivityManagerService = activityManagerService; - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - PriorityDump.dump(mPriorityDumper, fd, pw, args); - } - } - static class CacheBinder extends Binder { ActivityManagerService mActivityManagerService; @@ -2425,13 +1995,12 @@ public class ActivityManagerService extends IActivityManager.Stub mPlatformCompat = null; mProcessList = injector.getProcessList(this); mProcessList.init(this, activeUids, mPlatformCompat); - mLowMemDetector = null; + mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null); mOomAdjuster = hasHandlerThread ? new OomAdjuster(this, mProcessList, activeUids, handlerThread) : null; mIntentFirewall = hasHandlerThread ? new IntentFirewall(new IntentFirewallInterface(), mHandler) : null; - mProcessCpuThread = null; mProcessStats = null; mCpHelper = new ContentProviderHelper(this, false); // For the usage of {@link ActiveServices#cleanUpServices} that may be invoked from @@ -2482,7 +2051,8 @@ public class ActivityManagerService extends IActivityManager.Stub Context.PLATFORM_COMPAT_SERVICE); mProcessList = mInjector.getProcessList(this); mProcessList.init(this, activeUids, mPlatformCompat); - mLowMemDetector = new LowMemDetector(this); + mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), + new LowMemDetector(this)); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); // Broadcast policy parameters @@ -2554,40 +2124,6 @@ public class ActivityManagerService extends IActivityManager.Stub DisplayThread.get().getLooper()); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); - mProcessCpuThread = new Thread("CpuTracker") { - @Override - public void run() { - synchronized (mProcessCpuTracker) { - mProcessCpuInitLatch.countDown(); - mProcessCpuTracker.init(); - } - while (true) { - try { - try { - synchronized(this) { - final long now = SystemClock.uptimeMillis(); - long nextCpuDelay = (mLastCpuTime.get()+MONITOR_CPU_MAX_TIME)-now; - long nextWriteDelay = (mLastWriteTime+BATTERY_STATS_TIME)-now; - //Slog.i(TAG, "Cpu delay=" + nextCpuDelay - // + ", write delay=" + nextWriteDelay); - if (nextWriteDelay < nextCpuDelay) { - nextCpuDelay = nextWriteDelay; - } - if (nextCpuDelay > 0) { - mProcessCpuMutexFree.set(true); - this.wait(nextCpuDelay); - } - } - } catch (InterruptedException e) { - } - updateCpuStatsNow(); - } catch (Exception e) { - Slog.e(TAG, "Unexpected exception collecting process stats", e); - } - } - } - }; - mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext); Watchdog.getInstance().addMonitor(this); @@ -2621,7 +2157,6 @@ public class ActivityManagerService extends IActivityManager.Stub private void start() { removeAllProcessGroups(); - mProcessCpuThread.start(); mBatteryStatsService.publish(); mAppOpsService.publish(); @@ -2629,16 +2164,7 @@ public class ActivityManagerService extends IActivityManager.Stub LocalServices.addService(ActivityManagerInternal.class, mInternal); mActivityTaskManager.onActivityManagerInternalAdded(); mPendingIntentController.onActivityManagerInternalAdded(); - // Wait for the synchronized block started in mProcessCpuThread, - // so that any other access to mProcessCpuTracker from main thread - // will be blocked during mProcessCpuTracker initialization. - try { - mProcessCpuInitLatch.await(); - } catch (InterruptedException e) { - Slog.wtf(TAG, "Interrupted wait during start", e); - Thread.currentThread().interrupt(); - throw new IllegalStateException("Interrupted wait during start"); - } + mAppProfiler.onActivityManagerInternalAdded(); } public void initPowerManagement() { @@ -2781,116 +2307,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } - void updateCpuStats() { - final long now = SystemClock.uptimeMillis(); - if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) { - return; - } - if (mProcessCpuMutexFree.compareAndSet(true, false)) { - synchronized (mProcessCpuThread) { - mProcessCpuThread.notify(); - } - } + void updateCpuStatsLocked() { + mAppProfiler.updateCpuStatsLocked(); } void updateCpuStatsNow() { - synchronized (mProcessCpuTracker) { - mProcessCpuMutexFree.set(false); - final long now = SystemClock.uptimeMillis(); - boolean haveNewCpuStats = false; - - if (MONITOR_CPU_USAGE && - mLastCpuTime.get() < (now-MONITOR_CPU_MIN_TIME)) { - mLastCpuTime.set(now); - mProcessCpuTracker.update(); - if (mProcessCpuTracker.hasGoodLastStats()) { - haveNewCpuStats = true; - //Slog.i(TAG, mProcessCpu.printCurrentState()); - //Slog.i(TAG, "Total CPU usage: " - // + mProcessCpu.getTotalCpuPercent() + "%"); - - // Slog the cpu usage if the property is set. - if ("true".equals(SystemProperties.get("events.cpu"))) { - int user = mProcessCpuTracker.getLastUserTime(); - int system = mProcessCpuTracker.getLastSystemTime(); - int iowait = mProcessCpuTracker.getLastIoWaitTime(); - int irq = mProcessCpuTracker.getLastIrqTime(); - int softIrq = mProcessCpuTracker.getLastSoftIrqTime(); - int idle = mProcessCpuTracker.getLastIdleTime(); - - int total = user + system + iowait + irq + softIrq + idle; - if (total == 0) total = 1; - - EventLogTags.writeCpu( - ((user + system + iowait + irq + softIrq) * 100) / total, - (user * 100) / total, - (system * 100) / total, - (iowait * 100) / total, - (irq * 100) / total, - (softIrq * 100) / total); - } - } - } - - final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics(); - synchronized (bstats) { - if (haveNewCpuStats) { - if (bstats.startAddingCpuLocked()) { - int totalUTime = 0; - int totalSTime = 0; - final int statsCount = mProcessCpuTracker.countStats(); - final long elapsedRealtime = SystemClock.elapsedRealtime(); - final long uptime = SystemClock.uptimeMillis(); - synchronized (mPidsSelfLocked) { - for (int i = 0; i < statsCount; i++) { - ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); - if (!st.working) { - continue; - } - ProcessRecord pr = mPidsSelfLocked.get(st.pid); - totalUTime += st.rel_utime; - totalSTime += st.rel_stime; - if (pr != null) { - BatteryStatsImpl.Uid.Proc ps = pr.curProcBatteryStats; - if (ps == null || !ps.isActive()) { - pr.curProcBatteryStats = ps = bstats.getProcessStatsLocked( - pr.info.uid, pr.processName, - elapsedRealtime, uptime); - } - ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); - pr.curCpuTime += st.rel_utime + st.rel_stime; - if (pr.lastCpuTime == 0) { - pr.lastCpuTime = pr.curCpuTime; - } - } else { - BatteryStatsImpl.Uid.Proc ps = st.batteryStats; - if (ps == null || !ps.isActive()) { - st.batteryStats = ps = bstats.getProcessStatsLocked( - bstats.mapUid(st.uid), st.name, - elapsedRealtime, uptime); - } - ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); - } - } - } - - final int userTime = mProcessCpuTracker.getLastUserTime(); - final int systemTime = mProcessCpuTracker.getLastSystemTime(); - final int iowaitTime = mProcessCpuTracker.getLastIoWaitTime(); - final int irqTime = mProcessCpuTracker.getLastIrqTime(); - final int softIrqTime = mProcessCpuTracker.getLastSoftIrqTime(); - final int idleTime = mProcessCpuTracker.getLastIdleTime(); - bstats.finishAddingCpuLocked(totalUTime, totalSTime, userTime, - systemTime, iowaitTime, irqTime, softIrqTime, idleTime); - } - } - - if (mLastWriteTime < (now-BATTERY_STATS_TIME)) { - mLastWriteTime = now; - mBatteryStatsService.scheduleWriteToDisk(); - } - } - } + mAppProfiler.updateCpuStatsNow(); } @Override @@ -3510,9 +2932,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } - if (mProfileData.getProfileProc() == app) { - clearProfilerLocked(); - } + mAppProfiler.onAppDiedLocked(app); mAtmInternal.handleAppDied(app.getWindowProcessController(), restarting, () -> { Slog.w(TAG, "Crash of app " + app.processName @@ -3556,7 +2976,7 @@ public class ActivityManagerService extends IActivityManager.Stub // and the app that died was not running instrumentation, // then tell everyone we are now low on memory. if (!mProcessList.haveBackgroundProcessLocked()) { - boolean doReport = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + boolean doReport = Build.IS_DEBUGGABLE; if (doReport) { long now = SystemClock.uptimeMillis(); if (now < (mLastMemUsageReportTime+5*60*1000)) { @@ -3641,11 +3061,11 @@ public class ActivityManagerService extends IActivityManager.Stub "Process " + app.processName + " (pid " + pid + ") has died: " + ProcessList.makeOomAdjString(app.setAdj, true) + " " + ProcessList.makeProcStateString(app.setProcState), app.info.uid); - mAllowLowerMemLevel = true; + mAppProfiler.setAllowLowerMemLevelLocked(true); } else { // Note that we always want to do oom adj to update our state with the // new number of procs. - mAllowLowerMemLevel = false; + mAppProfiler.setAllowLowerMemLevelLocked(false); doLowMem = false; } EventLogTags.writeAmProcDied(app.userId, app.pid, app.processName, app.setAdj, @@ -4141,7 +3561,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { // Allow memory level to go down (the flag needs to be set before updating oom adj) // because this method is also used to simulate low memory. - mAllowLowerMemLevel = true; + mAppProfiler.setAllowLowerMemLevelLocked(true); mProcessList.killPackageProcessesLocked(null /* packageName */, -1 /* appId */, UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED, @@ -4926,48 +4346,7 @@ public class ActivityManagerService extends IActivityManager.Stub ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info; app.compat = compatibilityInfoForPackage(appInfo); - ProfilerInfo profilerInfo = null; - String preBindAgent = null; - if (mProfileData.getProfileApp() != null - && mProfileData.getProfileApp().equals(processName)) { - mProfileData.setProfileProc(app); - if (mProfileData.getProfilerInfo() != null) { - // Send a profiler info object to the app if either a file is given, or - // an agent should be loaded at bind-time. - boolean needsInfo = mProfileData.getProfilerInfo().profileFile != null - || mProfileData.getProfilerInfo().attachAgentDuringBind; - profilerInfo = needsInfo - ? new ProfilerInfo(mProfileData.getProfilerInfo()) : null; - if (mProfileData.getProfilerInfo().agent != null) { - preBindAgent = mProfileData.getProfilerInfo().agent; - } - } - } else if (instr != null && instr.mProfileFile != null) { - profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false, - null, false); - } - if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) { - // We need to do a debuggable check here. See setAgentApp for why the check is - // postponed to here. - if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - String agent = mAppAgentMap.get(processName); - // Do not overwrite already requested agent. - if (profilerInfo == null) { - profilerInfo = new ProfilerInfo(null, null, 0, false, false, - mAppAgentMap.get(processName), true); - } else if (profilerInfo.agent == null) { - profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true); - } - } - } - - if (profilerInfo != null && profilerInfo.profileFd != null) { - profilerInfo.profileFd = profilerInfo.profileFd.dup(); - if (TextUtils.equals(mProfileData.getProfileApp(), processName) - && mProfileData.getProfilerInfo() != null) { - clearProfilerLocked(); - } - } + ProfilerInfo profilerInfo = mAppProfiler.setupProfilerInfoLocked(thread, app, instr); // We deprecated Build.SERIAL and it is not accessible to // Instant Apps and target APIs higher than O MR1. Since access to the serial @@ -4976,44 +4355,6 @@ public class ActivityManagerService extends IActivityManager.Stub && appInfo.targetSdkVersion < Build.VERSION_CODES.P) ? sTheRealBuildSerial : Build.UNKNOWN; - // Check if this is a secondary process that should be incorporated into some - // currently active instrumentation. (Note we do this AFTER all of the profiling - // stuff above because profiling can currently happen only in the primary - // instrumentation process.) - if (mActiveInstrumentation.size() > 0 && instr == null) { - for (int i = mActiveInstrumentation.size() - 1; - i >= 0 && app.getActiveInstrumentation() == null; i--) { - ActiveInstrumentation aInstr = mActiveInstrumentation.get(i); - if (!aInstr.mFinished && aInstr.mTargetInfo.uid == app.uid) { - if (aInstr.mTargetProcesses.length == 0) { - // This is the wildcard mode, where every process brought up for - // the target instrumentation should be included. - if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) { - app.setActiveInstrumentation(aInstr); - aInstr.mRunningProcesses.add(app); - } - } else { - for (String proc : aInstr.mTargetProcesses) { - if (proc.equals(app.processName)) { - app.setActiveInstrumentation(aInstr); - aInstr.mRunningProcesses.add(app); - break; - } - } - } - } - } - } - - // If we were asked to attach an agent on startup, do so now, before we're binding - // application code. - if (preBindAgent != null) { - thread.attachAgent(preBindAgent); - } - if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - thread.attachStartupAgents(app.info.dataDir); - } - // Figure out whether the app needs to run in autofill compat mode. AutofillOptions autofillOptions = null; if (UserHandle.getAppId(app.info.uid) >= Process.FIRST_APPLICATION_UID) { @@ -5302,7 +4643,8 @@ public class ActivityManagerService extends IActivityManager.Stub boolean sticky, int sendingUser) { synchronized (ActivityManagerService.this) { mOomAdjuster.mCachedAppOptimizer.compactAllSystem(); - requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); + mAppProfiler.requestPssAllProcsLocked( + SystemClock.uptimeMillis(), true, false); } } }); @@ -6902,32 +6244,13 @@ public class ActivityManagerService extends IActivityManager.Stub "Requires permission " + android.Manifest.permission.SET_ACTIVITY_WATCHER); } - if (agent == null) { - if (mAppAgentMap != null) { - mAppAgentMap.remove(packageName); - if (mAppAgentMap.isEmpty()) { - mAppAgentMap = null; - } - } - } else { - if (mAppAgentMap == null) { - mAppAgentMap = new HashMap<>(); - } - if (mAppAgentMap.size() >= 100) { - // Limit the size of the map, to avoid OOMEs. - Slog.e(TAG, "App agent map has too many entries, cannot add " + packageName - + "/" + agent); - return; - } - mAppAgentMap.put(packageName, agent); - } + mAppProfiler.setAgentAppLocked(packageName, agent); } } void setTrackAllocationApp(ApplicationInfo app, String processName) { synchronized (this) { - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { + if (!Build.IS_DEBUGGABLE) { if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { throw new SecurityException("Process not debuggable: " + app.packageName); } @@ -6939,8 +6262,7 @@ public class ActivityManagerService extends IActivityManager.Stub void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo) { synchronized (this) { - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { + if (!Build.IS_DEBUGGABLE) { boolean isAppDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; boolean isAppProfileable = app.isProfileableByShell(); if (!isAppDebuggable && !isAppProfileable) { @@ -6948,24 +6270,12 @@ public class ActivityManagerService extends IActivityManager.Stub + "and not profileable by shell: " + app.packageName); } } - mProfileData.setProfileApp(processName); - - if (mProfileData.getProfilerInfo() != null) { - if (mProfileData.getProfilerInfo().profileFd != null) { - try { - mProfileData.getProfilerInfo().profileFd.close(); - } catch (IOException e) { - } - } - } - mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo)); - mProfileType = 0; + mAppProfiler.setProfileAppLocked(processName, profilerInfo); } } void setNativeDebuggingAppLocked(ApplicationInfo app, String processName) { - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { + if (!Build.IS_DEBUGGABLE) { if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { throw new SecurityException("Process not debuggable: " + app.packageName); } @@ -7047,7 +6357,7 @@ public class ActivityManagerService extends IActivityManager.Stub return; } synchronized (this) { - startHeapDumpLocked(pr, true); + mAppProfiler.startHeapDumpLocked(pr, true); } } @@ -7823,10 +7133,6 @@ public class ActivityManagerService extends IActivityManager.Stub br.onReceive(mContext, intent); } - private long getLowRamTimeSinceIdle(long now) { - return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now-mLowRamStartTime) : 0); - } - @Override public void performIdleMaintenance() { if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) @@ -7842,12 +7148,9 @@ public class ActivityManagerService extends IActivityManager.Stub // Compact all non-zygote processes to freshen up the page cache. mOomAdjuster.mCachedAppOptimizer.compactAllSystem(); - final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now); + final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLocked(now); mLastIdleTime = now; - mLowRamTimeSinceLastIdle = 0; - if (mLowRamStartTime != 0) { - mLowRamStartTime = now; - } + mAppProfiler.updateLowRamTimestampLocked(now); StringBuilder sb = new StringBuilder(128); sb.append("Idle maintenance over "); @@ -7898,8 +7201,7 @@ public class ActivityManagerService extends IActivityManager.Stub && proc.setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) { proc.notCachedSinceIdle = true; proc.initialIdlePss = 0; - proc.nextPssTime = ProcessList.computeNextPssTime(proc.setProcState, null, - mTestPssMode, mAtmInternal.isSleeping(), now); + mAppProfiler.updateNextPssTimeLocked(proc.setProcState, proc, now, true); } } } @@ -7937,11 +7239,7 @@ public class ActivityManagerService extends IActivityManager.Stub NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS); mHiddenApiBlacklist.registerObserver(); - final long pssDeferralMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - ACTIVITY_START_PSS_DEFER_CONFIG, 0L); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - ActivityThread.currentApplication().getMainExecutor(), - mPssDelayConfigListener); + mAppProfiler.retrieveSettings(); synchronized (this) { mDebugApp = mOrigDebugApp = debugApp; @@ -7960,7 +7258,6 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers, delayUserDataLocking); mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; - mPssDeferralTime = pssDeferralMs; } } @@ -8950,7 +8247,7 @@ public class ActivityManagerService extends IActivityManager.Stub public int getMemoryTrimLevel() { enforceNotIsolatedCaller("getMyMemoryState"); synchronized (this) { - return mLastMemoryLevel; + return mAppProfiler.getLastMemoryLevelLocked(); } } @@ -9961,7 +9258,7 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage); needSep = mAtmInternal.dumpForProcesses(fd, pw, dumpAll, dumpPackage, dumpAppId, needSep, - mTestPssMode, mWakefulness); + mAppProfiler.getTestPssModeLocked(), mWakefulness); if (dumpAll && mProcessList.mPendingStarts.size() > 0) { if (needSep) pw.println(); @@ -10005,35 +9302,7 @@ public class ActivityManagerService extends IActivityManager.Stub + " mOrigWaitForDebugger=" + mOrigWaitForDebugger); } } - if (mMemWatchProcesses.getMap().size() > 0) { - pw.println(" Mem watch processes:"); - final ArrayMap<String, SparseArray<Pair<Long, String>>> procs - = mMemWatchProcesses.getMap(); - for (int i=0; i<procs.size(); i++) { - final String proc = procs.keyAt(i); - final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); - for (int j=0; j<uids.size(); j++) { - if (needSep) { - pw.println(); - needSep = false; - } - StringBuilder sb = new StringBuilder(); - sb.append(" ").append(proc).append('/'); - UserHandle.formatUid(sb, uids.keyAt(j)); - Pair<Long, String> val = uids.valueAt(j); - sb.append(": "); DebugUtils.sizeValueToString(val.first, sb); - if (val.second != null) { - sb.append(", report to ").append(val.second); - } - pw.println(sb.toString()); - } - } - pw.print(" mMemWatchDumpProcName="); pw.println(mMemWatchDumpProcName); - pw.print(" mMemWatchDumpUri="); pw.println(mMemWatchDumpUri); - pw.print(" mMemWatchDumpPid="); pw.println(mMemWatchDumpPid); - pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid); - pw.print(" mMemWatchIsUserInitiated="); pw.println(mMemWatchIsUserInitiated); - } + needSep = mAppProfiler.dumpMemWatchProcessesLocked(pw, needSep); if (mTrackAllocationApp != null) { if (dumpPackage == null || dumpPackage.equals(mTrackAllocationApp)) { if (needSep) { @@ -10043,29 +9312,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mTrackAllocationApp=" + mTrackAllocationApp); } } - if (mProfileData.getProfileApp() != null || mProfileData.getProfileProc() != null - || (mProfileData.getProfilerInfo() != null && - (mProfileData.getProfilerInfo().profileFile != null - || mProfileData.getProfilerInfo().profileFd != null))) { - if (dumpPackage == null || dumpPackage.equals(mProfileData.getProfileApp())) { - if (needSep) { - pw.println(); - needSep = false; - } - pw.println(" mProfileApp=" + mProfileData.getProfileApp() - + " mProfileProc=" + mProfileData.getProfileProc()); - if (mProfileData.getProfilerInfo() != null) { - pw.println(" mProfileFile=" + mProfileData.getProfilerInfo().profileFile - + " mProfileFd=" + mProfileData.getProfilerInfo().profileFd); - pw.println(" mSamplingInterval=" - + mProfileData.getProfilerInfo().samplingInterval + - " mAutoStopProfiler=" - + mProfileData.getProfilerInfo().autoStopProfiler + - " mStreamingOutput=" + mProfileData.getProfilerInfo().streamingOutput); - pw.println(" mProfileType=" + mProfileType); - } - } - } + needSep = mAppProfiler.dumpProfileDataLocked(pw, dumpPackage, needSep); if (mNativeDebuggingApp != null) { if (dumpPackage == null || dumpPackage.equals(mNativeDebuggingApp)) { if (needSep) { @@ -10093,14 +9340,13 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(""); mOomAdjuster.dumpSequenceNumbersLocked(pw); mOomAdjuster.dumpProcCountsLocked(pw); - pw.println(" mAllowLowerMemLevel=" + mAllowLowerMemLevel - + " mLastMemoryLevel=" + mLastMemoryLevel - + " mLastNumProcesses=" + mLastNumProcesses); + mAppProfiler.dumpMemoryLevelsLocked(pw); long now = SystemClock.uptimeMillis(); pw.print(" mLastIdleTime="); TimeUtils.formatDuration(now, mLastIdleTime, pw); pw.print(" mLowRamSinceLastIdle="); - TimeUtils.formatDuration(getLowRamTimeSinceIdle(now), pw); + TimeUtils.formatDuration( + mAppProfiler.getLowRamTimeSinceIdleLocked(now), pw); pw.println(); pw.println(); @@ -10248,7 +9494,8 @@ public class ActivityManagerService extends IActivityManager.Stub dumpPackage); mAppErrors.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS, dumpPackage); - mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness, mTestPssMode); + mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness, + mAppProfiler.getTestPssModeLocked()); if (dumpPackage == null) { mUserController.dumpDebug(proto, @@ -10285,42 +9532,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } - if (mMemWatchProcesses.getMap().size() > 0) { - final long token = proto.start(ActivityManagerServiceDumpProcessesProto.MEM_WATCH_PROCESSES); - ArrayMap<String, SparseArray<Pair<Long, String>>> procs = mMemWatchProcesses.getMap(); - for (int i=0; i<procs.size(); i++) { - final String proc = procs.keyAt(i); - final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); - final long ptoken = proto.start(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.PROCS); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.NAME, proc); - for (int j=0; j<uids.size(); j++) { - final long utoken = proto.start(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.MEM_STATS); - Pair<Long, String> val = uids.valueAt(j); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.MemStats.UID, uids.keyAt(j)); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.MemStats.SIZE, - DebugUtils.sizeValueToString(val.first, new StringBuilder())); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.MemStats.REPORT_TO, val.second); - proto.end(utoken); - } - proto.end(ptoken); - } - - final long dtoken = proto.start(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.DUMP); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.PROC_NAME, - mMemWatchDumpProcName); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.URI, - mMemWatchDumpUri.toString()); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.PID, - mMemWatchDumpPid); - proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.UID, - mMemWatchDumpUid); - proto.write( - ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.IS_USER_INITIATED, - mMemWatchIsUserInitiated); - proto.end(dtoken); - - proto.end(token); - } + mAppProfiler.writeMemWatchProcessToProtoLocked(proto); if (mTrackAllocationApp != null) { if (dumpPackage == null || dumpPackage.equals(mTrackAllocationApp)) { @@ -10329,25 +9541,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } - if (mProfileData.getProfileApp() != null || mProfileData.getProfileProc() != null - || (mProfileData.getProfilerInfo() != null && - (mProfileData.getProfilerInfo().profileFile != null - || mProfileData.getProfilerInfo().profileFd != null))) { - if (dumpPackage == null || dumpPackage.equals(mProfileData.getProfileApp())) { - final long token = proto.start(ActivityManagerServiceDumpProcessesProto.PROFILE); - proto.write(ActivityManagerServiceDumpProcessesProto.Profile.APP_NAME, - mProfileData.getProfileApp()); - mProfileData.getProfileProc().dumpDebug(proto, - ActivityManagerServiceDumpProcessesProto.Profile.PROC); - if (mProfileData.getProfilerInfo() != null) { - mProfileData.getProfilerInfo().dumpDebug(proto, - ActivityManagerServiceDumpProcessesProto.Profile.INFO); - proto.write(ActivityManagerServiceDumpProcessesProto.Profile.TYPE, - mProfileType); - } - proto.end(token); - } - } + mAppProfiler.writeProfileDataToProtoLocked(proto, dumpPackage); if (dumpPackage == null || dumpPackage.equals(mNativeDebuggingApp)) { proto.write(ActivityManagerServiceDumpProcessesProto.NATIVE_DEBUGGING_APP, mNativeDebuggingApp); @@ -10365,12 +9559,11 @@ public class ActivityManagerService extends IActivityManager.Stub proto.write(ActivityManagerServiceDumpProcessesProto.BOOT_ANIMATION_COMPLETE, mBootAnimationComplete); proto.write(ActivityManagerServiceDumpProcessesProto.LAST_POWER_CHECK_UPTIME_MS, mLastPowerCheckUptime); mOomAdjuster.dumpProcessListVariablesLocked(proto); - proto.write(ActivityManagerServiceDumpProcessesProto.ALLOW_LOWER_MEM_LEVEL, mAllowLowerMemLevel); - proto.write(ActivityManagerServiceDumpProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel); - proto.write(ActivityManagerServiceDumpProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses); + mAppProfiler.writeMemoryLevelsToProtoLocked(proto); long now = SystemClock.uptimeMillis(); ProtoUtils.toDuration(proto, ActivityManagerServiceDumpProcessesProto.LAST_IDLE_TIME, mLastIdleTime, now); - proto.write(ActivityManagerServiceDumpProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS, getLowRamTimeSinceIdle(now)); + proto.write(ActivityManagerServiceDumpProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS, + mAppProfiler.getLowRamTimeSinceIdleLocked(now)); } } @@ -11553,7 +10746,7 @@ public class ActivityManagerService extends IActivityManager.Stub } private final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix, - MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief, + MemoryUsageDumpOptions opts, final String[] innerArgs, boolean brief, ArrayList<ProcessRecord> procs, PrintWriter categoryPw) { long uptime = SystemClock.uptimeMillis(); long realtime = SystemClock.elapsedRealtime(); @@ -11573,16 +10766,13 @@ public class ActivityManagerService extends IActivityManager.Stub findPid = Integer.parseInt(innerArgs[0]); } catch (NumberFormatException e) { } - synchronized (mProcessCpuTracker) { - final int N = mProcessCpuTracker.countStats(); - for (int i=0; i<N; i++) { - ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); - if (st.pid == findPid || (st.baseName != null - && st.baseName.equals(innerArgs[0]))) { - nativeProcs.add(st); - } + final int fFindPid = findPid; + mAppProfiler.forAllCpuStats((st) -> { + if (st.pid == fFindPid || (st.baseName != null + && st.baseName.equals(innerArgs[0]))) { + nativeProcs.add(st); } - } + }); if (nativeProcs.size() > 0) { dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact); @@ -11640,21 +10830,13 @@ public class ActivityManagerService extends IActivityManager.Stub ArrayList<MemItem> procMems = new ArrayList<MemItem>(); final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>(); - long nativePss = 0; - long nativeSwapPss = 0; - long nativeRss = 0; - long dalvikPss = 0; - long dalvikSwapPss = 0; - long dalvikRss = 0; + final long[] ss = new long[INDEX_LAST]; long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : EmptyArray.LONG; long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : EmptyArray.LONG; long[] dalvikSubitemRss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : EmptyArray.LONG; - long otherPss = 0; - long otherSwapPss = 0; - long otherRss = 0; long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; @@ -11665,7 +10847,6 @@ public class ActivityManagerService extends IActivityManager.Stub ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[]) new ArrayList[DUMP_MEM_OOM_LABEL.length]; - long totalPss = 0; long totalSwapPss = 0; long totalRss = 0; long cachedPss = 0; @@ -11774,40 +10955,40 @@ public class ActivityManagerService extends IActivityManager.Stub } if (!opts.isCheckinRequest && mi != null) { - totalPss += myTotalPss; - totalSwapPss += myTotalSwapPss; - totalRss += myTotalRss; + ss[INDEX_TOTAL_PSS] += myTotalPss; + ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss; + ss[INDEX_TOTAL_RSS] += myTotalRss; MemItem pssItem = new MemItem(r.processName + " (pid " + pid + (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss, myTotalSwapPss, myTotalRss, pid, hasActivities); procMems.add(pssItem); procMemsMap.put(pid, pssItem); - nativePss += mi.nativePss; - nativeSwapPss += mi.nativeSwappedOutPss; - nativeRss += mi.nativeRss; - dalvikPss += mi.dalvikPss; - dalvikSwapPss += mi.dalvikSwappedOutPss; - dalvikRss += mi.dalvikRss; + ss[INDEX_NATIVE_PSS] += mi.nativePss; + ss[INDEX_NATIVE_SWAP_PSS] += mi.nativeSwappedOutPss; + ss[INDEX_NATIVE_RSS] += mi.nativeRss; + ss[INDEX_DALVIK_PSS] += mi.dalvikPss; + ss[INDEX_DALVIK_SWAP_PSS] += mi.dalvikSwappedOutPss; + ss[INDEX_DALVIK_RSS] += mi.dalvikRss; for (int j=0; j<dalvikSubitemPss.length; j++) { dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); dalvikSubitemSwapPss[j] += mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); dalvikSubitemRss[j] += mi.getOtherRss(Debug.MemoryInfo.NUM_OTHER_STATS + j); } - otherPss += mi.otherPss; - otherRss += mi.otherRss; - otherSwapPss += mi.otherSwappedOutPss; + ss[INDEX_OTHER_PSS] += mi.otherPss; + ss[INDEX_OTHER_RSS] += mi.otherRss; + ss[INDEX_OTHER_SWAP_PSS] += mi.otherSwappedOutPss; for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { long mem = mi.getOtherPss(j); miscPss[j] += mem; - otherPss -= mem; + ss[INDEX_OTHER_PSS] -= mem; mem = mi.getOtherSwappedOutPss(j); miscSwapPss[j] += mem; - otherSwapPss -= mem; + ss[INDEX_OTHER_SWAP_PSS] -= mem; mem = mi.getOtherRss(j); miscRss[j] += mem; - otherRss -= mem; + ss[INDEX_OTHER_RSS] -= mem; } if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) { @@ -11837,86 +11018,87 @@ public class ActivityManagerService extends IActivityManager.Stub if (collectNative) { mi = null; - synchronized (mProcessCpuTracker) { - final int N = mProcessCpuTracker.countStats(); - for (int i=0; i<N; i++) { - ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); - if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { - if (mi == null) { - mi = new Debug.MemoryInfo(); + final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1]; + mAppProfiler.forAllCpuStats((st) -> { + if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + if (memInfos[0] == null) { + memInfos[0] = new Debug.MemoryInfo(); + } + final Debug.MemoryInfo info = memInfos[0]; + if (!brief && !opts.oomOnly) { + if (!Debug.getMemoryInfo(st.pid, info)) { + return; } - if (!brief && !opts.oomOnly) { - if (!Debug.getMemoryInfo(st.pid, mi)) { - continue; - } - } else { - long pss = Debug.getPss(st.pid, tmpLong, null); - if (pss == 0) { - continue; - } - mi.nativePss = (int) pss; - mi.nativePrivateDirty = (int) tmpLong[0]; - mi.nativeRss = (int) tmpLong[2]; + } else { + long pss = Debug.getPss(st.pid, tmpLong, null); + if (pss == 0) { + return; } + info.nativePss = (int) pss; + info.nativePrivateDirty = (int) tmpLong[0]; + info.nativeRss = (int) tmpLong[2]; + } - final long myTotalPss = mi.getTotalPss(); - final long myTotalSwapPss = mi.getTotalSwappedOutPss(); - final long myTotalRss = mi.getTotalRss(); - totalPss += myTotalPss; - totalSwapPss += myTotalSwapPss; - totalRss += myTotalRss; - nativeProcTotalPss += myTotalPss; - - MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", - st.name, myTotalPss, mi.getSummaryTotalSwapPss(), myTotalRss, - st.pid, false); - procMems.add(pssItem); - - nativePss += mi.nativePss; - nativeSwapPss += mi.nativeSwappedOutPss; - nativeRss += mi.nativeRss; - dalvikPss += mi.dalvikPss; - dalvikSwapPss += mi.dalvikSwappedOutPss; - dalvikRss += mi.dalvikRss; - for (int j=0; j<dalvikSubitemPss.length; j++) { - dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); - dalvikSubitemSwapPss[j] += - mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); - dalvikSubitemRss[j] += mi.getOtherRss(Debug.MemoryInfo.NUM_OTHER_STATS - + j); - } - otherPss += mi.otherPss; - otherSwapPss += mi.otherSwappedOutPss; - otherRss += mi.otherRss; - for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { - long mem = mi.getOtherPss(j); - miscPss[j] += mem; - otherPss -= mem; - mem = mi.getOtherSwappedOutPss(j); - miscSwapPss[j] += mem; - otherSwapPss -= mem; - mem = mi.getOtherRss(j); - miscRss[j] += mem; - otherRss -= mem; - } - oomPss[0] += myTotalPss; - oomSwapPss[0] += myTotalSwapPss; - if (oomProcs[0] == null) { - oomProcs[0] = new ArrayList<MemItem>(); - } - oomProcs[0].add(pssItem); - oomRss[0] += myTotalRss; + final long myTotalPss = info.getTotalPss(); + final long myTotalSwapPss = info.getTotalSwappedOutPss(); + final long myTotalRss = info.getTotalRss(); + ss[INDEX_TOTAL_PSS] += myTotalPss; + ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss; + ss[INDEX_TOTAL_RSS] += myTotalRss; + ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss; + + MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", + st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss, + st.pid, false); + procMems.add(pssItem); + + ss[INDEX_NATIVE_PSS] += info.nativePss; + ss[INDEX_NATIVE_SWAP_PSS] += info.nativeSwappedOutPss; + ss[INDEX_NATIVE_RSS] += info.nativeRss; + ss[INDEX_DALVIK_PSS] += info.dalvikPss; + ss[INDEX_DALVIK_SWAP_PSS] += info.dalvikSwappedOutPss; + ss[INDEX_DALVIK_RSS] += info.dalvikRss; + for (int j = 0; j < dalvikSubitemPss.length; j++) { + dalvikSubitemPss[j] += info.getOtherPss( + Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikSubitemSwapPss[j] += + info.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikSubitemRss[j] += info.getOtherRss(Debug.MemoryInfo.NUM_OTHER_STATS + + j); } + ss[INDEX_OTHER_PSS] += info.otherPss; + ss[INDEX_OTHER_SWAP_PSS] += info.otherSwappedOutPss; + ss[INDEX_OTHER_RSS] += info.otherRss; + for (int j = 0; j < Debug.MemoryInfo.NUM_OTHER_STATS; j++) { + long mem = info.getOtherPss(j); + miscPss[j] += mem; + ss[INDEX_OTHER_PSS] -= mem; + mem = info.getOtherSwappedOutPss(j); + miscSwapPss[j] += mem; + ss[INDEX_OTHER_SWAP_PSS] -= mem; + mem = info.getOtherRss(j); + miscRss[j] += mem; + ss[INDEX_OTHER_RSS] -= mem; + } + oomPss[0] += myTotalPss; + oomSwapPss[0] += myTotalSwapPss; + if (oomProcs[0] == null) { + oomProcs[0] = new ArrayList<MemItem>(); + } + oomProcs[0].add(pssItem); + oomRss[0] += myTotalRss; } - } + }); ArrayList<MemItem> catMems = new ArrayList<MemItem>(); - catMems.add(new MemItem("Native", "Native", nativePss, nativeSwapPss, nativeRss, -1)); + catMems.add(new MemItem("Native", "Native", + ss[INDEX_NATIVE_PSS], ss[INDEX_NATIVE_SWAP_PSS], ss[INDEX_NATIVE_RSS], -1)); final int dalvikId = -2; - catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, dalvikRss, - dalvikId)); - catMems.add(new MemItem("Unknown", "Unknown", otherPss, otherSwapPss, otherRss, -3)); + catMems.add(new MemItem("Dalvik", "Dalvik", ss[INDEX_DALVIK_PSS], + ss[INDEX_DALVIK_SWAP_PSS], ss[INDEX_DALVIK_RSS], dalvikId)); + catMems.add(new MemItem("Unknown", "Unknown", ss[INDEX_OTHER_PSS], + ss[INDEX_OTHER_SWAP_PSS], ss[INDEX_OTHER_RSS], -3)); for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { String label = Debug.MemoryInfo.getOtherLabel(j); catMems.add(new MemItem(label, label, miscPss[j], miscSwapPss[j], miscRss[j], j)); @@ -11982,7 +11164,7 @@ public class ActivityManagerService extends IActivityManager.Stub } dumpMemItems(out, " ", "cat", catMems, true, opts.isCompact, false, false); } - opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0; + opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && ss[INDEX_TOTAL_SWAP_PSS] != 0; if (!brief && !opts.oomOnly && !opts.isCompact) { pw.println(); pw.println("Total PSS by process:"); @@ -12008,40 +11190,23 @@ public class ActivityManagerService extends IActivityManager.Stub } MemInfoReader memInfo = new MemInfoReader(); memInfo.readMemInfo(); - if (nativeProcTotalPss > 0) { + if (ss[INDEX_TOTAL_NATIVE_PSS] > 0) { synchronized (mProcessStats.mLock) { final long cachedKb = memInfo.getCachedSizeKb(); final long freeKb = memInfo.getFreeSizeKb(); final long zramKb = memInfo.getZramTotalSizeKb(); final long kernelKb = memInfo.getKernelUsedSizeKb(); - EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, - kernelKb*1024, nativeProcTotalPss*1024); + EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024, + kernelKb * 1024, ss[INDEX_TOTAL_NATIVE_PSS] * 1024); mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, - nativeProcTotalPss); + ss[INDEX_TOTAL_NATIVE_PSS]); } } if (!brief) { if (!opts.isCompact) { pw.print("Total RAM: "); pw.print(stringifyKBSize(memInfo.getTotalSizeKb())); pw.print(" (status "); - switch (mLastMemoryLevel) { - case ProcessStats.ADJ_MEM_FACTOR_NORMAL: - pw.println("normal)"); - break; - case ProcessStats.ADJ_MEM_FACTOR_MODERATE: - pw.println("moderate)"); - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - pw.println("low)"); - break; - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - pw.println("critical)"); - break; - default: - pw.print(mLastMemoryLevel); - pw.println(")"); - break; - } + mAppProfiler.dumpLastMemoryLevelLocked(pw); pw.print(" Free RAM: "); pw.print(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb() + memInfo.getFreeSizeKb())); @@ -12056,7 +11221,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print("ram,"); pw.print(memInfo.getTotalSizeKb()); pw.print(","); pw.print(cachedPss + memInfo.getCachedSizeKb() + memInfo.getFreeSizeKb()); pw.print(","); - pw.println(totalPss - cachedPss); + pw.println(ss[INDEX_TOTAL_PSS] - cachedPss); } } long kernelUsed = memInfo.getKernelUsedSizeKb(); @@ -12078,13 +11243,14 @@ public class ActivityManagerService extends IActivityManager.Stub // set on ION VMAs, therefore consider the entire ION heap as used kernel memory kernelUsed += ionHeap; } - final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) + final long lostRAM = memInfo.getTotalSizeKb() + - (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS]) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() - kernelUsed - memInfo.getZramTotalSizeKb(); if (!opts.isCompact) { - pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss + pw.print(" Used RAM: "); pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss + kernelUsed)); pw.print(" ("); - pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + "); + pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); pw.print(" used pss + "); pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n"); pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM)); } else { @@ -12158,7 +11324,7 @@ public class ActivityManagerService extends IActivityManager.Stub } private final void dumpApplicationMemoryUsage(FileDescriptor fd, - MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief, + MemoryUsageDumpOptions opts, final String[] innerArgs, boolean brief, ArrayList<ProcessRecord> procs) { final long uptimeMs = SystemClock.uptimeMillis(); final long realtimeMs = SystemClock.elapsedRealtime(); @@ -12170,7 +11336,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (innerArgs.length > 0) { proc = innerArgs[0]; if (proc.charAt(0) != '-') { - ArrayList<ProcessCpuTracker.Stats> nativeProcs + final ArrayList<ProcessCpuTracker.Stats> nativeProcs = new ArrayList<ProcessCpuTracker.Stats>(); updateCpuStatsNow(); int findPid = -1; @@ -12178,16 +11344,13 @@ public class ActivityManagerService extends IActivityManager.Stub findPid = Integer.parseInt(innerArgs[0]); } catch (NumberFormatException e) { } - synchronized (mProcessCpuTracker) { - final int N = mProcessCpuTracker.countStats(); - for (int i=0; i<N; i++) { - ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); - if (st.pid == findPid || (st.baseName != null - && st.baseName.equals(innerArgs[0]))) { - nativeProcs.add(st); - } + final int fFindPid = findPid; + mAppProfiler.forAllCpuStats((st) -> { + if (st.pid == fFindPid || (st.baseName != null + && st.baseName.equals(innerArgs[0]))) { + nativeProcs.add(st); } - } + }); if (nativeProcs.size() > 0) { ProtoOutputStream proto = new ProtoOutputStream(fd); @@ -12251,36 +11414,25 @@ public class ActivityManagerService extends IActivityManager.Stub proto.write(MemInfoDumpProto.UPTIME_DURATION_MS, uptimeMs); proto.write(MemInfoDumpProto.ELAPSED_REALTIME_MS, realtimeMs); - ArrayList<MemItem> procMems = new ArrayList<MemItem>(); + final ArrayList<MemItem> procMems = new ArrayList<MemItem>(); final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>(); - long nativePss = 0; - long nativeSwapPss = 0; - long nativeRss = 0; - long dalvikPss = 0; - long dalvikSwapPss = 0; - long dalvikRss = 0; + final long[] ss = new long[INDEX_LAST]; long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : EmptyArray.LONG; long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : EmptyArray.LONG; long[] dalvikSubitemRss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : EmptyArray.LONG; - long otherPss = 0; - long otherSwapPss = 0; - long otherRss = 0; long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; - long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; - long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length]; - long[] oomRss = new long[DUMP_MEM_OOM_LABEL.length]; - ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[]) + final long[] oomPss = new long[DUMP_MEM_OOM_LABEL.length]; + final long[] oomSwapPss = new long[DUMP_MEM_OOM_LABEL.length]; + final long[] oomRss = new long[DUMP_MEM_OOM_LABEL.length]; + final ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[]) new ArrayList[DUMP_MEM_OOM_LABEL.length]; - long totalPss = 0; - long totalSwapPss = 0; - long totalRss = 0; long cachedPss = 0; long cachedSwapPss = 0; boolean hasSwapPss = false; @@ -12381,40 +11533,40 @@ public class ActivityManagerService extends IActivityManager.Stub } if (!opts.isCheckinRequest && mi != null) { - totalPss += myTotalPss; - totalSwapPss += myTotalSwapPss; - totalRss += myTotalRss; + ss[INDEX_TOTAL_PSS] += myTotalPss; + ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss; + ss[INDEX_TOTAL_RSS] += myTotalRss; MemItem pssItem = new MemItem(r.processName + " (pid " + pid + (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss, myTotalSwapPss, myTotalRss, pid, hasActivities); procMems.add(pssItem); procMemsMap.put(pid, pssItem); - nativePss += mi.nativePss; - nativeSwapPss += mi.nativeSwappedOutPss; - nativeRss += mi.nativeRss; - dalvikPss += mi.dalvikPss; - dalvikSwapPss += mi.dalvikSwappedOutPss; - dalvikRss += mi.dalvikRss; + ss[INDEX_NATIVE_PSS] += mi.nativePss; + ss[INDEX_NATIVE_SWAP_PSS] += mi.nativeSwappedOutPss; + ss[INDEX_NATIVE_RSS] += mi.nativeRss; + ss[INDEX_DALVIK_PSS] += mi.dalvikPss; + ss[INDEX_DALVIK_SWAP_PSS] += mi.dalvikSwappedOutPss; + ss[INDEX_DALVIK_RSS] += mi.dalvikRss; for (int j=0; j<dalvikSubitemPss.length; j++) { dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); dalvikSubitemSwapPss[j] += mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); dalvikSubitemRss[j] += mi.getOtherRss(Debug.MemoryInfo.NUM_OTHER_STATS + j); } - otherPss += mi.otherPss; - otherRss += mi.otherRss; - otherSwapPss += mi.otherSwappedOutPss; + ss[INDEX_OTHER_PSS] += mi.otherPss; + ss[INDEX_OTHER_RSS] += mi.otherRss; + ss[INDEX_OTHER_SWAP_PSS] += mi.otherSwappedOutPss; for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { long mem = mi.getOtherPss(j); miscPss[j] += mem; - otherPss -= mem; + ss[INDEX_OTHER_PSS] -= mem; mem = mi.getOtherSwappedOutPss(j); miscSwapPss[j] += mem; - otherSwapPss -= mem; + ss[INDEX_OTHER_SWAP_PSS] -= mem; mem = mi.getOtherRss(j); miscRss[j] += mem; - otherRss -= mem; + ss[INDEX_OTHER_RSS] -= mem; } if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) { @@ -12443,86 +11595,87 @@ public class ActivityManagerService extends IActivityManager.Stub if (collectNative) { mi = null; - synchronized (mProcessCpuTracker) { - final int N = mProcessCpuTracker.countStats(); - for (int i=0; i<N; i++) { - ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); - if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { - if (mi == null) { - mi = new Debug.MemoryInfo(); + final Debug.MemoryInfo[] memInfos = new Debug.MemoryInfo[1]; + mAppProfiler.forAllCpuStats((st) -> { + if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + if (memInfos[0] == null) { + memInfos[0] = new Debug.MemoryInfo(); + } + final Debug.MemoryInfo info = memInfos[0]; + if (!brief && !opts.oomOnly) { + if (!Debug.getMemoryInfo(st.pid, info)) { + return; } - if (!brief && !opts.oomOnly) { - if (!Debug.getMemoryInfo(st.pid, mi)) { - continue; - } - } else { - long pss = Debug.getPss(st.pid, tmpLong, null); - if (pss == 0) { - continue; - } - mi.nativePss = (int) pss; - mi.nativePrivateDirty = (int) tmpLong[0]; - mi.nativeRss = (int) tmpLong[2]; + } else { + long pss = Debug.getPss(st.pid, tmpLong, null); + if (pss == 0) { + return; } + info.nativePss = (int) pss; + info.nativePrivateDirty = (int) tmpLong[0]; + info.nativeRss = (int) tmpLong[2]; + } - final long myTotalPss = mi.getTotalPss(); - final long myTotalSwapPss = mi.getTotalSwappedOutPss(); - final long myTotalRss = mi.getTotalRss(); - totalPss += myTotalPss; - totalSwapPss += myTotalSwapPss; - totalRss += myTotalRss; - nativeProcTotalPss += myTotalPss; - - MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", - st.name, myTotalPss, mi.getSummaryTotalSwapPss(), myTotalRss, - st.pid, false); - procMems.add(pssItem); - - nativePss += mi.nativePss; - nativeSwapPss += mi.nativeSwappedOutPss; - nativeRss += mi.nativeRss; - dalvikPss += mi.dalvikPss; - dalvikSwapPss += mi.dalvikSwappedOutPss; - dalvikRss += mi.dalvikRss; - for (int j=0; j<dalvikSubitemPss.length; j++) { - dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); - dalvikSubitemSwapPss[j] += - mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); - dalvikSubitemRss[j] += mi.getOtherRss(Debug.MemoryInfo.NUM_OTHER_STATS - + j); - } - otherPss += mi.otherPss; - otherSwapPss += mi.otherSwappedOutPss; - otherRss += mi.otherRss; - for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { - long mem = mi.getOtherPss(j); - miscPss[j] += mem; - otherPss -= mem; - mem = mi.getOtherSwappedOutPss(j); - miscSwapPss[j] += mem; - otherSwapPss -= mem; - mem = mi.getOtherRss(j); - miscRss[j] += mem; - otherRss -= mem; - } - oomPss[0] += myTotalPss; - oomSwapPss[0] += myTotalSwapPss; - if (oomProcs[0] == null) { - oomProcs[0] = new ArrayList<MemItem>(); - } - oomProcs[0].add(pssItem); - oomRss[0] += myTotalRss; + final long myTotalPss = info.getTotalPss(); + final long myTotalSwapPss = info.getTotalSwappedOutPss(); + final long myTotalRss = info.getTotalRss(); + ss[INDEX_TOTAL_PSS] += myTotalPss; + ss[INDEX_TOTAL_SWAP_PSS] += myTotalSwapPss; + ss[INDEX_TOTAL_RSS] += myTotalRss; + ss[INDEX_TOTAL_NATIVE_PSS] += myTotalPss; + + MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", + st.name, myTotalPss, info.getSummaryTotalSwapPss(), myTotalRss, + st.pid, false); + procMems.add(pssItem); + + ss[INDEX_NATIVE_PSS] += info.nativePss; + ss[INDEX_NATIVE_SWAP_PSS] += info.nativeSwappedOutPss; + ss[INDEX_NATIVE_RSS] += info.nativeRss; + ss[INDEX_DALVIK_PSS] += info.dalvikPss; + ss[INDEX_DALVIK_SWAP_PSS] += info.dalvikSwappedOutPss; + ss[INDEX_DALVIK_RSS] += info.dalvikRss; + for (int j = 0; j < dalvikSubitemPss.length; j++) { + dalvikSubitemPss[j] += info.getOtherPss( + Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikSubitemSwapPss[j] += + info.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikSubitemRss[j] += info.getOtherRss(Debug.MemoryInfo.NUM_OTHER_STATS + + j); } + ss[INDEX_OTHER_PSS] += info.otherPss; + ss[INDEX_OTHER_SWAP_PSS] += info.otherSwappedOutPss; + ss[INDEX_OTHER_RSS] += info.otherRss; + for (int j = 0; j < Debug.MemoryInfo.NUM_OTHER_STATS; j++) { + long mem = info.getOtherPss(j); + miscPss[j] += mem; + ss[INDEX_OTHER_PSS] -= mem; + mem = info.getOtherSwappedOutPss(j); + miscSwapPss[j] += mem; + ss[INDEX_OTHER_SWAP_PSS] -= mem; + mem = info.getOtherRss(j); + miscRss[j] += mem; + ss[INDEX_OTHER_RSS] -= mem; + } + oomPss[0] += myTotalPss; + oomSwapPss[0] += myTotalSwapPss; + if (oomProcs[0] == null) { + oomProcs[0] = new ArrayList<MemItem>(); + } + oomProcs[0].add(pssItem); + oomRss[0] += myTotalRss; } - } + }); ArrayList<MemItem> catMems = new ArrayList<MemItem>(); - catMems.add(new MemItem("Native", "Native", nativePss, nativeSwapPss, nativeRss, -1)); + catMems.add(new MemItem("Native", "Native", ss[INDEX_NATIVE_PSS], + ss[INDEX_NATIVE_SWAP_PSS], ss[INDEX_NATIVE_RSS], -1)); final int dalvikId = -2; - catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, dalvikRss, - dalvikId)); - catMems.add(new MemItem("Unknown", "Unknown", otherPss, otherSwapPss, otherRss, -3)); + catMems.add(new MemItem("Dalvik", "Dalvik", ss[INDEX_DALVIK_PSS], + ss[INDEX_DALVIK_SWAP_PSS], ss[INDEX_DALVIK_RSS], dalvikId)); + catMems.add(new MemItem("Unknown", "Unknown", ss[INDEX_OTHER_PSS], + ss[INDEX_OTHER_SWAP_PSS], ss[INDEX_OTHER_RSS], -3)); for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { String label = Debug.MemoryInfo.getOtherLabel(j); catMems.add(new MemItem(label, label, miscPss[j], miscSwapPss[j], miscRss[j], j)); @@ -12579,7 +11732,7 @@ public class ActivityManagerService extends IActivityManager.Stub catMems, true, false, false); } - opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0; + opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && ss[INDEX_TOTAL_SWAP_PSS] != 0; if (!opts.oomOnly) { dumpMemItems(proto, MemInfoDumpProto.TOTAL_PSS_BY_PROCESS, "proc", procMems, true, true, opts.dumpSwapPss); @@ -12592,29 +11745,30 @@ public class ActivityManagerService extends IActivityManager.Stub } MemInfoReader memInfo = new MemInfoReader(); memInfo.readMemInfo(); - if (nativeProcTotalPss > 0) { + if (ss[INDEX_TOTAL_NATIVE_PSS] > 0) { synchronized (mProcessStats.mLock) { final long cachedKb = memInfo.getCachedSizeKb(); final long freeKb = memInfo.getFreeSizeKb(); final long zramKb = memInfo.getZramTotalSizeKb(); final long kernelKb = memInfo.getKernelUsedSizeKb(); - EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, - kernelKb*1024, nativeProcTotalPss*1024); + EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024, + kernelKb * 1024, ss[INDEX_TOTAL_NATIVE_PSS] * 1024); mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, - nativeProcTotalPss); + ss[INDEX_TOTAL_NATIVE_PSS]); } } if (!brief) { proto.write(MemInfoDumpProto.TOTAL_RAM_KB, memInfo.getTotalSizeKb()); - proto.write(MemInfoDumpProto.STATUS, mLastMemoryLevel); + proto.write(MemInfoDumpProto.STATUS, mAppProfiler.getLastMemoryLevelLocked()); proto.write(MemInfoDumpProto.CACHED_PSS_KB, cachedPss); proto.write(MemInfoDumpProto.CACHED_KERNEL_KB, memInfo.getCachedSizeKb()); proto.write(MemInfoDumpProto.FREE_KB, memInfo.getFreeSizeKb()); } - long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) + long lostRAM = memInfo.getTotalSizeKb() + - (ss[INDEX_TOTAL_PSS] - ss[INDEX_TOTAL_SWAP_PSS]) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb(); - proto.write(MemInfoDumpProto.USED_PSS_KB, totalPss - cachedPss); + proto.write(MemInfoDumpProto.USED_PSS_KB, ss[INDEX_TOTAL_PSS] - cachedPss); proto.write(MemInfoDumpProto.USED_KERNEL_KB, memInfo.getKernelUsedSizeKb()); proto.write(MemInfoDumpProto.LOST_RAM_KB, lostRAM); if (!brief) { @@ -12685,13 +11839,8 @@ public class ActivityManagerService extends IActivityManager.Stub updateCpuStatsNow(); long[] memtrackTmp = new long[1]; long[] swaptrackTmp = new long[2]; - final List<ProcessCpuTracker.Stats> stats; // Get a list of Stats that have vsize > 0 - synchronized (mProcessCpuTracker) { - stats = mProcessCpuTracker.getStats((st) -> { - return st.vsize > 0; - }); - } + final List<ProcessCpuTracker.Stats> stats = mAppProfiler.getCpuStats(st -> st.vsize > 0); final int statsCount = stats.size(); for (int i = 0; i < statsCount; i++) { ProcessCpuTracker.Stats st = stats.get(i); @@ -12986,8 +12135,7 @@ public class ActivityManagerService extends IActivityManager.Stub } mProcessesToGc.remove(app); - mPendingPssProcesses.remove(app); - ProcessList.abortNextPssTime(app.procStateMemTracker); + mAppProfiler.onCleanupApplicationRecordLocked(app); // Dismiss any open dialogs. app.getDialogController().clearAllErrorDialogs(); @@ -15538,229 +14686,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - /** @hide */ - public static Uri makeHeapDumpUri(String procName) { - return Uri.parse("content://com.android.shell.heapdump/" + procName + "_javaheap.bin"); - } - - private final class RecordPssRunnable implements Runnable { - private final ProcessRecord mProc; - private final Uri mDumpUri; - private final ContentResolver mContentResolver; - - RecordPssRunnable(ProcessRecord proc, Uri dumpUri, ContentResolver contentResolver) { - mProc = proc; - mDumpUri = dumpUri; - mContentResolver = contentResolver; - } - - @Override - public void run() { - try (ParcelFileDescriptor fd = mContentResolver.openFileDescriptor(mDumpUri, "rw")) { - IApplicationThread thread = mProc.thread; - if (thread != null) { - try { - if (DEBUG_PSS) { - Slog.d(TAG_PSS, "Requesting dump heap from " - + mProc + " to " + mDumpUri.getPath()); - } - thread.dumpHeap(/* managed= */ true, - /* mallocInfo= */ false, /* runGc= */ false, - mDumpUri.getPath(), fd, - /* finishCallback= */ null); - } catch (RemoteException e) { - } - } - } catch (IOException e) { - Slog.e(TAG, "Failed to dump heap", e); - // Need to clear the heap dump variables, otherwise no further heap dumps will be - // attempted. - abortHeapDump(mProc.processName); - } - } - } - - /** - * Record new PSS sample for a process. - */ - void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, long swapPss, - long rss, int statType, long pssDuration, long now) { - EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024, - swapPss * 1024, rss * 1024, statType, procState, pssDuration); - proc.lastPssTime = now; - synchronized (mProcessStats.mLock) { - proc.baseProcessTracker.addPss( - pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList); - } - for (int ipkg = proc.pkgList.mPkgList.size() - 1; ipkg >= 0; ipkg--) { - ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg); - FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, - proc.info.uid, - holder.state.getName(), - holder.state.getPackage(), - pss, uss, rss, statType, pssDuration, - holder.appVersion); - } - if (DEBUG_PSS) Slog.d(TAG_PSS, - "pss of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss - + " state=" + ProcessList.makeProcStateString(procState)); - if (proc.initialIdlePss == 0) { - proc.initialIdlePss = pss; - } - proc.lastPss = pss; - proc.lastSwapPss = swapPss; - if (procState >= ActivityManager.PROCESS_STATE_HOME) { - proc.lastCachedPss = pss; - proc.lastCachedSwapPss = swapPss; - } - proc.mLastRss = rss; - - final SparseArray<Pair<Long, String>> watchUids - = mMemWatchProcesses.getMap().get(proc.processName); - Long check = null; - if (watchUids != null) { - Pair<Long, String> val = watchUids.get(proc.uid); - if (val == null) { - val = watchUids.get(0); - } - if (val != null) { - check = val.first; - } - } - if (check != null) { - if ((pss * 1024) >= check && proc.thread != null && mMemWatchDumpProcName == null) { - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { - if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - isDebuggable = true; - } - } - if (isDebuggable) { - Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting"); - startHeapDumpLocked(proc, false); - } else { - Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check - + ", but debugging not enabled"); - } - } - } - } - - private void startHeapDumpLocked(ProcessRecord proc, boolean isUserInitiated) { - mMemWatchDumpProcName = proc.processName; - mMemWatchDumpUri = makeHeapDumpUri(proc.processName); - mMemWatchDumpPid = proc.pid; - mMemWatchDumpUid = proc.uid; - mMemWatchIsUserInitiated = isUserInitiated; - Context ctx; - try { - ctx = mContext.createPackageContextAsUser("android", 0, - UserHandle.getUserHandleForUid(mMemWatchDumpUid)); - } catch (NameNotFoundException e) { - throw new RuntimeException("android package not found."); - } - BackgroundThread.getHandler().post( - new RecordPssRunnable(proc, mMemWatchDumpUri, ctx.getContentResolver())); - } - - /** - * Schedule PSS collection of a process. - */ - boolean requestPssLocked(ProcessRecord proc, int procState) { - if (mPendingPssProcesses.contains(proc)) { - return false; - } - if (mPendingPssProcesses.size() == 0) { - final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0) - ? mPssDeferralTime : 0; - if (DEBUG_PSS && deferral > 0) { - Slog.d(TAG_PSS, "requestPssLocked() deferring PSS request by " - + deferral + " ms"); - } - mBgHandler.sendEmptyMessageDelayed(COLLECT_PSS_BG_MSG, deferral); - } - if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + proc); - proc.pssProcState = procState; - proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE; - mPendingPssProcesses.add(proc); - return true; - } - - /** - * Re-defer a posted PSS collection pass, if one exists. Assumes deferral is - * currently active policy when called. - */ - private void deferPssIfNeededLocked() { - if (mPendingPssProcesses.size() > 0) { - mBgHandler.removeMessages(COLLECT_PSS_BG_MSG); - mBgHandler.sendEmptyMessageDelayed(COLLECT_PSS_BG_MSG, mPssDeferralTime); - } - } - - private void deferPssForActivityStart() { - synchronized (ActivityManagerService.this) { - if (mPssDeferralTime > 0) { - if (DEBUG_PSS) { - Slog.d(TAG_PSS, "Deferring PSS collection for activity start"); - } - deferPssIfNeededLocked(); - mActivityStartingNesting.getAndIncrement(); - mBgHandler.sendEmptyMessageDelayed(STOP_DEFERRING_PSS_MSG, mPssDeferralTime); - } - } - } - - /** - * Schedule PSS collection of all processes. - */ - void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) { - if (!always) { - if (now < (mLastFullPssTime + - (memLowered ? mConstants.FULL_PSS_LOWERED_INTERVAL - : mConstants.FULL_PSS_MIN_INTERVAL))) { - return; - } - } - if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); - mLastFullPssTime = now; - mFullPssPending = true; - for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) { - ProcessList.abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker);; - } - mPendingPssProcesses.ensureCapacity(mProcessList.getLruSizeLocked()); - mPendingPssProcesses.clear(); - for (int i = mProcessList.getLruSizeLocked() - 1; i >= 0; i--) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (app.thread == null || app.getCurProcState() == PROCESS_STATE_NONEXISTENT) { - continue; - } - if (memLowered || (always && now > - app.lastStateTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) - || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { - app.pssProcState = app.setProcState; - app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL - : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM; - app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), - app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now); - mPendingPssProcesses.add(app); - } - } - if (!mBgHandler.hasMessages(COLLECT_PSS_BG_MSG)) { - mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); - } - } - - public void setTestPssMode(boolean enabled) { - synchronized (this) { - mTestPssMode = enabled; - if (enabled) { - // Whenever we enable the mode, we want to take a snapshot all of current - // process mem use. - requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, true); - } - } - } - /** * Ask a given process to GC right now. */ @@ -16116,187 +15041,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") - final boolean updateLowMemStateLocked(int numCached, int numEmpty, int numTrimming) { - final int N = mProcessList.getLruSizeLocked(); - final long now = SystemClock.uptimeMillis(); - int memFactor; - if (mLowMemDetector != null && mLowMemDetector.isAvailable()) { - memFactor = mLowMemDetector.getMemFactor(); - } else { - // Now determine the memory trimming level of background processes. - // Unfortunately we need to start at the back of the list to do this - // properly. We only do this if the number of background apps we - // are managing to keep around is less than half the maximum we desire; - // if we are keeping a good number around, we'll let them use whatever - // memory they want. - if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES - && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) { - final int numCachedAndEmpty = numCached + numEmpty; - if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { - memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL; - } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { - memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW; - } else { - memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE; - } - } else { - memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL; - } - } - // We always allow the memory level to go up (better). We only allow it to go - // down if we are in a state where that is allowed, *and* the total number of processes - // has gone down since last time. - if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor - + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel - + " numProcs=" + mProcessList.getLruSizeLocked() + " last=" + mLastNumProcesses); - if (memFactor > mLastMemoryLevel) { - if (!mAllowLowerMemLevel || mProcessList.getLruSizeLocked() >= mLastNumProcesses) { - memFactor = mLastMemoryLevel; - if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!"); - } - } - if (memFactor != mLastMemoryLevel) { - EventLogTags.writeAmMemFactor(memFactor, mLastMemoryLevel); - FrameworkStatsLog.write(FrameworkStatsLog.MEMORY_FACTOR_STATE_CHANGED, memFactor); - } - mLastMemoryLevel = memFactor; - mLastNumProcesses = mProcessList.getLruSizeLocked(); - boolean allChanged; - int trackerMemFactor; - synchronized (mProcessStats.mLock) { - allChanged = mProcessStats.setMemFactorLocked( - memFactor, mAtmInternal != null ? !mAtmInternal.isSleeping() : true, now); - trackerMemFactor = mProcessStats.getMemFactorLocked(); - } - if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) { - if (mLowRamStartTime == 0) { - mLowRamStartTime = now; - } - int step = 0; - int fgTrimLevel; - switch (memFactor) { - case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: - fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; - break; - case ProcessStats.ADJ_MEM_FACTOR_LOW: - fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; - break; - default: - fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; - break; - } - int factor = numTrimming/3; - int minFactor = 2; - if (mAtmInternal.getHomeProcess() != null) minFactor++; - if (mAtmInternal.getPreviousProcess() != null) minFactor++; - if (factor < minFactor) factor = minFactor; - int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; - for (int i=N-1; i>=0; i--) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (allChanged || app.procStateChanged) { - setProcessTrackerStateLocked(app, trackerMemFactor, now); - app.procStateChanged = false; - } - if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME - && !app.killedByAm) { - if (app.trimMemoryLevel < curLevel && app.thread != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, - "Trimming memory of " + app.processName + " to " + curLevel); - app.thread.scheduleTrimMemory(curLevel); - } catch (RemoteException e) { - } - } - app.trimMemoryLevel = curLevel; - step++; - if (step >= factor) { - step = 0; - switch (curLevel) { - case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: - curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE; - break; - case ComponentCallbacks2.TRIM_MEMORY_MODERATE: - curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; - break; - } - } - } else if (app.getCurProcState() == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - && !app.killedByAm) { - if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND - && app.thread != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, - "Trimming memory of heavy-weight " + app.processName - + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); - app.thread.scheduleTrimMemory( - ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); - } catch (RemoteException e) { - } - } - app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; - } else { - if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - || app.systemNoUi) && app.hasPendingUiClean()) { - // If this application is now in the background and it - // had done UI, then give it the special trim level to - // have it free UI resources. - final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; - if (app.trimMemoryLevel < level && app.thread != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, - "Trimming memory of bg-ui " + app.processName - + " to " + level); - app.thread.scheduleTrimMemory(level); - } catch (RemoteException e) { - } - } - app.setPendingUiClean(false); - } - if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, - "Trimming memory of fg " + app.processName - + " to " + fgTrimLevel); - app.thread.scheduleTrimMemory(fgTrimLevel); - } catch (RemoteException e) { - } - } - app.trimMemoryLevel = fgTrimLevel; - } - } - } else { - if (mLowRamStartTime != 0) { - mLowRamTimeSinceLastIdle += now - mLowRamStartTime; - mLowRamStartTime = 0; - } - for (int i=N-1; i>=0; i--) { - ProcessRecord app = mProcessList.mLruProcesses.get(i); - if (allChanged || app.procStateChanged) { - setProcessTrackerStateLocked(app, trackerMemFactor, now); - app.procStateChanged = false; - } - if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - || app.systemNoUi) && app.hasPendingUiClean()) { - if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN - && app.thread != null) { - try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, - "Trimming memory of ui hidden " + app.processName - + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - app.thread.scheduleTrimMemory( - ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - } catch (RemoteException e) { - } - } - app.setPendingUiClean(false); - } - app.trimMemoryLevel = 0; - } - } - return allChanged; - } - - @GuardedBy("this") final void updateOomAdjLocked(String oomAdjReason) { mOomAdjuster.updateOomAdjLocked(oomAdjReason); } @@ -16584,109 +15328,31 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private void stopProfilerLocked(ProcessRecord proc, int profileType) { - if (proc == null || proc == mProfileData.getProfileProc()) { - proc = mProfileData.getProfileProc(); - profileType = mProfileType; - clearProfilerLocked(); - } - if (proc == null) { - return; - } - try { - proc.thread.profilerControl(false, null, profileType); - } catch (RemoteException e) { - throw new IllegalStateException("Process disappeared"); - } - } - - void clearProfilerLocked() { - if (mProfileData.getProfilerInfo() != null - && mProfileData.getProfilerInfo().profileFd != null) { - try { - mProfileData.getProfilerInfo().profileFd.close(); - } catch (IOException e) { - } - } - mProfileData.setProfileApp(null); - mProfileData.setProfileProc(null); - mProfileData.setProfilerInfo(null); - } - public boolean profileControl(String process, int userId, boolean start, ProfilerInfo profilerInfo, int profileType) throws RemoteException { + synchronized (this) { + // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to + // its own permission. + if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SET_ACTIVITY_WATCHER); + } - try { - synchronized (this) { - // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to - // its own permission. - if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires permission " - + android.Manifest.permission.SET_ACTIVITY_WATCHER); - } - - if (start && (profilerInfo == null || profilerInfo.profileFd == null)) { - throw new IllegalArgumentException("null profile info or fd"); - } - - ProcessRecord proc = null; - if (process != null) { - proc = findProcessLocked(process, userId, "profileControl"); - } - - if (start && (proc == null || proc.thread == null)) { - throw new IllegalArgumentException("Unknown process: " + process); - } - - if (start) { - stopProfilerLocked(null, 0); - setProfileApp(proc.info, proc.processName, profilerInfo); - mProfileData.setProfileProc(proc); - mProfileType = profileType; - ParcelFileDescriptor fd = profilerInfo.profileFd; - try { - fd = fd.dup(); - } catch (IOException e) { - fd = null; - } - profilerInfo.profileFd = fd; - proc.thread.profilerControl(start, profilerInfo, profileType); - fd = null; - try { - mProfileData.getProfilerInfo().profileFd.close(); - } catch (IOException e) { - } - mProfileData.getProfilerInfo().profileFd = null; - - if (proc.pid == MY_PID) { - // When profiling the system server itself, avoid closing the file - // descriptor, as profilerControl will not create a copy. - // Note: it is also not correct to just set profileFd to null, as the - // whole ProfilerInfo instance is passed down! - profilerInfo = null; - } - } else { - stopProfilerLocked(proc, profileType); - if (profilerInfo != null && profilerInfo.profileFd != null) { - try { - profilerInfo.profileFd.close(); - } catch (IOException e) { - } - } - } + if (start && (profilerInfo == null || profilerInfo.profileFd == null)) { + throw new IllegalArgumentException("null profile info or fd"); + } - return true; + ProcessRecord proc = null; + if (process != null) { + proc = findProcessLocked(process, userId, "profileControl"); } - } catch (RemoteException e) { - throw new IllegalStateException("Process disappeared"); - } finally { - if (profilerInfo != null && profilerInfo.profileFd != null) { - try { - profilerInfo.profileFd.close(); - } catch (IOException e) { - } + + if (start && (proc == null || proc.thread == null)) { + throw new IllegalArgumentException("Unknown process: " + process); } + + return mAppProfiler.profileControlLocked(proc, start, profilerInfo, profileType); } } @@ -16746,7 +15412,7 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("Unknown process: " + process); } - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + boolean isDebuggable = Build.IS_DEBUGGABLE; if (!isDebuggable) { if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { throw new SecurityException("Process not debuggable: " + proc); @@ -16805,45 +15471,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } } - synchronized (this) { - if (maxMemSize > 0) { - mMemWatchProcesses.put(processName, uid, new Pair(maxMemSize, reportPackage)); - } else { - if (uid != 0) { - mMemWatchProcesses.remove(processName, uid); - } else { - mMemWatchProcesses.getMap().remove(processName); - } - } - } + mAppProfiler.setDumpHeapDebugLimit(processName, uid, maxMemSize, reportPackage); } @Override public void dumpHeapFinished(String path) { - synchronized (this) { - if (Binder.getCallingPid() != mMemWatchDumpPid) { - Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid() - + " does not match last pid " + mMemWatchDumpPid); - return; - } - if (mMemWatchDumpUri == null || !mMemWatchDumpUri.getPath().equals(path)) { - Slog.w(TAG, "dumpHeapFinished: Calling path " + path - + " does not match last path " + mMemWatchDumpUri); - return; - } - if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path); - mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG); - - // Forced gc to clean up the remnant hprof fd. - Runtime.getRuntime().gc(); - } - } - - /** Clear the currently executing heap dump variables so a new heap dump can be started. */ - private void abortHeapDump(String procName) { - Message msg = mHandler.obtainMessage(ABORT_DUMPHEAP_MSG); - msg.obj = procName; - mHandler.sendMessage(msg); + mAppProfiler.dumpHeapFinished(path, Binder.getCallingPid()); } /** In this method we try to acquire our lock to make sure that we have not deadlocked */ @@ -16995,8 +15628,7 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { + if (!Build.IS_DEBUGGABLE) { if ((process.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { return false; } @@ -17426,7 +16058,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void updateCpuStats() { synchronized (ActivityManagerService.this) { - ActivityManagerService.this.updateCpuStats(); + ActivityManagerService.this.updateCpuStatsLocked(); } } @@ -18345,8 +16977,7 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("Unknown process: " + process); } - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { + if (!Build.IS_DEBUGGABLE) { if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { throw new SecurityException("Process not debuggable: " + proc); } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 5d429d3065f3..3f55bffc1f3e 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -22,7 +22,6 @@ import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.MY_PID; -import static com.android.server.am.ActivityManagerService.SYSTEM_DEBUGGABLE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; @@ -36,10 +35,10 @@ import android.content.Intent; import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Message; import android.os.Process; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.ArrayMap; @@ -596,7 +595,7 @@ class AppErrors { return mService.mAtmInternal.handleAppCrashInActivityController( name, pid, shortMsg, longMsg, timeMillis, crashInfo.stackTrace, () -> { - if ("1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")) + if (Build.IS_DEBUGGABLE && "Native crash".equals(crashInfo.exceptionClassName)) { Slog.w(TAG, "Skip killing native crashed app " + name + "(" + pid + ") during testing"); diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java new file mode 100644 index 000000000000..0b5d5859e86e --- /dev/null +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -0,0 +1,1731 @@ +/* + * Copyright (C) 2020 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.server.am; + +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; +import static android.os.Process.FIRST_APPLICATION_UID; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; + +import android.annotation.BroadcastBehavior; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityThread; +import android.app.IApplicationThread; +import android.app.ProfilerInfo; +import android.content.ComponentCallbacks2; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Binder; +import android.os.Build; +import android.os.Debug; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.DebugUtils; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.ProcessMap; +import com.android.internal.app.procstats.ProcessStats; +import com.android.internal.os.BackgroundThread; +import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.MemInfoReader; +import com.android.server.am.ProcessList.ProcStateMemTracker; +import com.android.server.utils.PriorityDump; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * A helper class taking care of the profiling, memory and cpu sampling of apps + */ +public class AppProfiler { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; + + static final String TAG_PSS = TAG + POSTFIX_PSS; + + static final String TAG_OOM_ADJ = ActivityManagerService.TAG_OOM_ADJ; + + /** Control over CPU and battery monitoring */ + // write battery stats every 30 minutes. + static final long BATTERY_STATS_TIME = 30 * 60 * 1000; + + static final boolean MONITOR_CPU_USAGE = true; + + // don't sample cpu less than every 5 seconds. + static final long MONITOR_CPU_MIN_TIME = 5 * 1000; + + // wait possibly forever for next cpu sample. + static final long MONITOR_CPU_MAX_TIME = 0x0fffffff; + + static final boolean MONITOR_THREAD_CPU_USAGE = false; + + static final String ACTIVITY_START_PSS_DEFER_CONFIG = "activity_start_pss_defer"; + + /** + * Broadcast sent when heap dump collection has been completed. + */ + @BroadcastBehavior(includeBackground = true, protectedBroadcast = true) + private static final String ACTION_HEAP_DUMP_FINISHED = + "com.android.internal.intent.action.HEAP_DUMP_FINISHED"; + + /** + * The process we are reporting + */ + private static final String EXTRA_HEAP_DUMP_PROCESS_NAME = + "com.android.internal.extra.heap_dump.PROCESS_NAME"; + + /** + * The size limit the process reached. + */ + private static final String EXTRA_HEAP_DUMP_SIZE_BYTES = + "com.android.internal.extra.heap_dump.SIZE_BYTES"; + + /** + * Whether the user initiated the dump or not. + */ + private static final String EXTRA_HEAP_DUMP_IS_USER_INITIATED = + "com.android.internal.extra.heap_dump.IS_USER_INITIATED"; + + /** + * Optional name of package to directly launch. + */ + private static final String EXTRA_HEAP_DUMP_REPORT_PACKAGE = + "com.android.internal.extra.heap_dump.REPORT_PACKAGE"; + + /** + * How long we defer PSS gathering while activities are starting, in milliseconds. + * This is adjustable via DeviceConfig. If it is zero or negative, no PSS deferral + * is done. + */ + private volatile long mPssDeferralTime = 0; + + /** + * Processes we want to collect PSS data from. + */ + @GuardedBy("mService") + private final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>(); + + /** + * Depth of overlapping activity-start PSS deferral notes + */ + private final AtomicInteger mActivityStartingNesting = new AtomicInteger(0); + + /** + * Last time we requested PSS data of all processes. + */ + @GuardedBy("mService") + private long mLastFullPssTime = SystemClock.uptimeMillis(); + + /** + * If set, the next time we collect PSS data we should do a full collection + * with data from native processes and the kernel. + */ + @GuardedBy("mService") + private boolean mFullPssPending = false; + + /** + * If true, we are running under a test environment so will sample PSS from processes + * much more rapidly to try to collect better data when the tests are rapidly + * running through apps. + */ + @GuardedBy("mService") + private boolean mTestPssMode = false; + + @GuardedBy("mService") + private final LowMemDetector mLowMemDetector; + + /** + * Allow the current computed overall memory level of the system to go down? + * This is set to false when we are killing processes for reasons other than + * memory management, so that the now smaller process list will not be taken as + * an indication that memory is tighter. + */ + @GuardedBy("mService") + private boolean mAllowLowerMemLevel = false; + + /** + * The last computed memory level, for holding when we are in a state that + * processes are going away for other reasons. + */ + @GuardedBy("mService") + private int mLastMemoryLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; + + /** + * The last total number of process we have, to determine if changes actually look + * like a shrinking number of process due to lower RAM. + */ + @GuardedBy("mService") + private int mLastNumProcesses; + + /** + * Total time spent with RAM that has been added in the past since the last idle time. + */ + @GuardedBy("mService") + private long mLowRamTimeSinceLastIdle = 0; + + /** + * If RAM is currently low, when that horrible situation started. + */ + @GuardedBy("mService") + private long mLowRamStartTime = 0; + + /** + * Stores a map of process name -> agent string. When a process is started and mAgentAppMap + * is not null, this map is checked and the mapped agent installed during bind-time. Note: + * A non-null agent in mProfileInfo overrides this. + */ + private @Nullable Map<String, String> mAppAgentMap = null; + + private int mProfileType = 0; + private final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>(); + private String mMemWatchDumpProcName; + private Uri mMemWatchDumpUri; + private int mMemWatchDumpPid; + private int mMemWatchDumpUid; + private boolean mMemWatchIsUserInitiated; + + /** + * Used to collect per-process CPU use for ANRs, battery stats, etc. + * Must acquire this object's lock when accessing it. + * NOTE: this lock will be held while doing long operations (trawling + * through all processes in /proc), so it should never be acquired by + * any critical paths such as when holding the main activity manager lock. + */ + private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker( + MONITOR_THREAD_CPU_USAGE); + private final AtomicLong mLastCpuTime = new AtomicLong(0); + private final AtomicBoolean mProcessCpuMutexFree = new AtomicBoolean(true); + private final CountDownLatch mProcessCpuInitLatch = new CountDownLatch(1); + + private long mLastWriteTime = 0; + private final ProfileData mProfileData = new ProfileData(); + + /** + * Runtime CPU use collection thread. This object's lock is used to + * perform synchronization with the thread (notifying it to run). + */ + private final Thread mProcessCpuThread; + + private final ActivityManagerService mService; + private final Handler mBgHandler; + + /** + * Observe DeviceConfig changes to the PSS calculation interval + */ + private final DeviceConfig.OnPropertiesChangedListener mPssDelayConfigListener = + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(Properties properties) { + if (properties.getKeyset().contains(ACTIVITY_START_PSS_DEFER_CONFIG)) { + mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0); + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Activity-start PSS delay now " + + mPssDeferralTime + " ms"); + } + } + } + }; + + private class ProfileData { + private String mProfileApp = null; + private ProcessRecord mProfileProc = null; + private ProfilerInfo mProfilerInfo = null; + + void setProfileApp(String profileApp) { + mProfileApp = profileApp; + if (mService.mAtmInternal != null) { + mService.mAtmInternal.setProfileApp(profileApp); + } + } + + String getProfileApp() { + return mProfileApp; + } + + void setProfileProc(ProcessRecord profileProc) { + mProfileProc = profileProc; + if (mService.mAtmInternal != null) { + mService.mAtmInternal.setProfileProc(profileProc == null ? null + : profileProc.getWindowProcessController()); + } + } + + ProcessRecord getProfileProc() { + return mProfileProc; + } + + void setProfilerInfo(ProfilerInfo profilerInfo) { + mProfilerInfo = profilerInfo; + if (mService.mAtmInternal != null) { + mService.mAtmInternal.setProfilerInfo(profilerInfo); + } + } + + ProfilerInfo getProfilerInfo() { + return mProfilerInfo; + } + } + + private class BgHandler extends Handler { + static final int COLLECT_PSS_BG_MSG = 1; + static final int DEFER_PSS_MSG = 2; + static final int STOP_DEFERRING_PSS_MSG = 3; + BgHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case COLLECT_PSS_BG_MSG: + collectPssInBackground(); + break; + case DEFER_PSS_MSG: + deferPssForActivityStart(); + break; + case STOP_DEFERRING_PSS_MSG: + stopDeferPss(); + break; + } + } + } + + private void collectPssInBackground() { + long start = SystemClock.uptimeMillis(); + MemInfoReader memInfo = null; + synchronized (mService) { + if (mFullPssPending) { + mFullPssPending = false; + memInfo = new MemInfoReader(); + } + } + if (memInfo != null) { + updateCpuStatsNow(); + long nativeTotalPss = 0; + final List<ProcessCpuTracker.Stats> stats; + synchronized (mProcessCpuTracker) { + stats = mProcessCpuTracker.getStats(st -> { + return st.vsize > 0 && st.uid < FIRST_APPLICATION_UID; + }); + } + final int numOfStats = stats.size(); + for (int j = 0; j < numOfStats; j++) { + synchronized (mService.mPidsSelfLocked) { + if (mService.mPidsSelfLocked.indexOfKey(stats.get(j).pid) >= 0) { + // This is one of our own processes; skip it. + continue; + } + } + nativeTotalPss += Debug.getPss(stats.get(j).pid, null, null); + } + memInfo.readMemInfo(); + synchronized (mService.mProcessStats.mLock) { + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Collected native and kernel memory in " + + (SystemClock.uptimeMillis() - start) + "ms"); + } + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + EventLogTags.writeAmMeminfo(cachedKb * 1024, freeKb * 1024, zramKb * 1024, + kernelKb * 1024, nativeTotalPss * 1024); + mService.mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeTotalPss); + } + } + + int num = 0; + long[] tmp = new long[3]; + do { + ProcessRecord proc; + int procState; + int statType; + int pid = -1; + long lastPssTime; + synchronized (mService) { + if (mPendingPssProcesses.size() <= 0) { + if (mTestPssMode || DEBUG_PSS) { + Slog.d(TAG_PSS, + "Collected pss of " + num + " processes in " + + (SystemClock.uptimeMillis() - start) + "ms"); + } + mPendingPssProcesses.clear(); + return; + } + proc = mPendingPssProcesses.remove(0); + procState = proc.pssProcState; + statType = proc.pssStatType; + lastPssTime = proc.lastPssTime; + long now = SystemClock.uptimeMillis(); + if (proc.thread != null && procState == proc.setProcState + && (lastPssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) < now) { + pid = proc.pid; + } else { + abortNextPssTime(proc.procStateMemTracker); + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Skipped pss collection of " + pid + + ": still need " + + (lastPssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE - now) + + "ms until safe"); + } + proc = null; + pid = 0; + } + } + if (proc != null) { + long startTime = SystemClock.currentThreadTimeMillis(); + // skip background PSS calculation of apps that are capturing + // camera imagery + final boolean usingCamera = mService.isCameraActiveForUid(proc.uid); + long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null); + long endTime = SystemClock.currentThreadTimeMillis(); + synchronized (mService) { + if (pss != 0 && proc.thread != null && proc.setProcState == procState + && proc.pid == pid && proc.lastPssTime == lastPssTime) { + num++; + commitNextPssTime(proc.procStateMemTracker); + recordPssSampleLocked(proc, procState, pss, tmp[0], tmp[1], tmp[2], + statType, endTime - startTime, SystemClock.uptimeMillis()); + } else { + abortNextPssTime(proc.procStateMemTracker); + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Skipped pss collection of " + pid + + ": " + (proc.thread == null ? "NO_THREAD " : "") + + (usingCamera ? "CAMERA " : "") + + (proc.pid != pid ? "PID_CHANGED " : "") + + " initState=" + procState + " curState=" + + proc.setProcState + " " + + (proc.lastPssTime != lastPssTime ? "TIME_CHANGED" : "")); + } + } + } + } + } while (true); + } + + private static void commitNextPssTime(ProcStateMemTracker tracker) { + if (tracker.mPendingMemState >= 0) { + tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState; + tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor; + tracker.mTotalHighestMem = tracker.mPendingHighestMemState; + tracker.mPendingMemState = -1; + } + } + + private static void abortNextPssTime(ProcStateMemTracker tracker) { + tracker.mPendingMemState = -1; + } + + @GuardedBy("mService") + void updateNextPssTimeLocked(int procState, ProcessRecord app, long now, boolean forceUpdate) { + if (!forceUpdate) { + if (now <= app.nextPssTime + && now <= Math.max(app.lastPssTime + ProcessList.PSS_MAX_INTERVAL, + app.lastStateTime + ProcessList.minTimeFromStateChange(mTestPssMode))) { + // update is not due, ignore it. + return; + } + if (!requestPssLocked(app, app.setProcState)) { + return; + } + } + app.nextPssTime = ProcessList.computeNextPssTime(procState, app.procStateMemTracker, + mTestPssMode, mService.mAtmInternal.isSleeping(), now); + } + + /** + * Record new PSS sample for a process. + */ + @GuardedBy("mService") + private void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, + long swapPss, long rss, int statType, long pssDuration, long now) { + EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024, + swapPss * 1024, rss * 1024, statType, procState, pssDuration); + proc.lastPssTime = now; + synchronized (mService.mProcessStats.mLock) { + proc.baseProcessTracker.addPss( + pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList); + } + for (int ipkg = proc.pkgList.mPkgList.size() - 1; ipkg >= 0; ipkg--) { + ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg); + FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, + proc.info.uid, + holder.state.getName(), + holder.state.getPackage(), + pss, uss, rss, statType, pssDuration, + holder.appVersion); + } + if (DEBUG_PSS) { + Slog.d(TAG_PSS, + "pss of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss + + " state=" + ProcessList.makeProcStateString(procState)); + } + if (proc.initialIdlePss == 0) { + proc.initialIdlePss = pss; + } + proc.lastPss = pss; + proc.lastSwapPss = swapPss; + if (procState >= ActivityManager.PROCESS_STATE_HOME) { + proc.lastCachedPss = pss; + proc.lastCachedSwapPss = swapPss; + } + proc.mLastRss = rss; + + final SparseArray<Pair<Long, String>> watchUids = + mMemWatchProcesses.getMap().get(proc.processName); + Long check = null; + if (watchUids != null) { + Pair<Long, String> val = watchUids.get(proc.uid); + if (val == null) { + val = watchUids.get(0); + } + if (val != null) { + check = val.first; + } + } + if (check != null) { + if ((pss * 1024) >= check && proc.thread != null && mMemWatchDumpProcName == null) { + boolean isDebuggable = Build.IS_DEBUGGABLE; + if (!isDebuggable) { + if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + isDebuggable = true; + } + } + if (isDebuggable) { + Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting"); + startHeapDumpLocked(proc, false); + } else { + Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + + ", but debugging not enabled"); + } + } + } + } + + private final class RecordPssRunnable implements Runnable { + private final ProcessRecord mProc; + private final Uri mDumpUri; + private final ContentResolver mContentResolver; + + RecordPssRunnable(ProcessRecord proc, Uri dumpUri, ContentResolver contentResolver) { + mProc = proc; + mDumpUri = dumpUri; + mContentResolver = contentResolver; + } + + @Override + public void run() { + try (ParcelFileDescriptor fd = mContentResolver.openFileDescriptor(mDumpUri, "rw")) { + IApplicationThread thread = mProc.thread; + if (thread != null) { + try { + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Requesting dump heap from " + + mProc + " to " + mDumpUri.getPath()); + } + thread.dumpHeap(/* managed= */ true, + /* mallocInfo= */ false, /* runGc= */ false, + mDumpUri.getPath(), fd, + /* finishCallback= */ null); + } catch (RemoteException e) { + } + } + } catch (IOException e) { + Slog.e(TAG, "Failed to dump heap", e); + // Need to clear the heap dump variables, otherwise no further heap dumps will be + // attempted. + abortHeapDump(mProc.processName); + } + } + } + + @GuardedBy("mService") + void startHeapDumpLocked(ProcessRecord proc, boolean isUserInitiated) { + mMemWatchDumpProcName = proc.processName; + mMemWatchDumpUri = makeHeapDumpUri(proc.processName); + mMemWatchDumpPid = proc.pid; + mMemWatchDumpUid = proc.uid; + mMemWatchIsUserInitiated = isUserInitiated; + Context ctx; + try { + ctx = mService.mContext.createPackageContextAsUser("android", 0, + UserHandle.getUserHandleForUid(mMemWatchDumpUid)); + } catch (NameNotFoundException e) { + throw new RuntimeException("android package not found."); + } + BackgroundThread.getHandler().post( + new RecordPssRunnable(proc, mMemWatchDumpUri, ctx.getContentResolver())); + } + + void dumpHeapFinished(String path, int callerPid) { + synchronized (mService) { + if (callerPid != mMemWatchDumpPid) { + Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid() + + " does not match last pid " + mMemWatchDumpPid); + return; + } + if (mMemWatchDumpUri == null || !mMemWatchDumpUri.getPath().equals(path)) { + Slog.w(TAG, "dumpHeapFinished: Calling path " + path + + " does not match last path " + mMemWatchDumpUri); + return; + } + if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path); + mService.mHandler.sendEmptyMessage( + ActivityManagerService.POST_DUMP_HEAP_NOTIFICATION_MSG); + + // Forced gc to clean up the remnant hprof fd. + Runtime.getRuntime().gc(); + } + } + + void handlePostDumpHeapNotification() { + final String procName; + final int uid; + final long memLimit; + final String reportPackage; + final boolean isUserInitiated; + synchronized (mService) { + uid = mMemWatchDumpUid; + procName = mMemWatchDumpProcName; + Pair<Long, String> val = mMemWatchProcesses.get(procName, uid); + if (val == null) { + val = mMemWatchProcesses.get(procName, 0); + } + if (val != null) { + memLimit = val.first; + reportPackage = val.second; + } else { + memLimit = 0; + reportPackage = null; + } + isUserInitiated = mMemWatchIsUserInitiated; + + mMemWatchDumpUri = null; + mMemWatchDumpProcName = null; + mMemWatchDumpPid = -1; + mMemWatchDumpUid = -1; + } + if (procName == null) { + return; + } + + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Showing dump heap notification from " + procName + "/" + uid); + } + + Intent dumpFinishedIntent = new Intent(ACTION_HEAP_DUMP_FINISHED); + // Send this only to the Shell package. + dumpFinishedIntent.setPackage("com.android.shell"); + dumpFinishedIntent.putExtra(Intent.EXTRA_UID, uid); + dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_IS_USER_INITIATED, isUserInitiated); + dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_SIZE_BYTES, memLimit); + dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_REPORT_PACKAGE, reportPackage); + dumpFinishedIntent.putExtra(EXTRA_HEAP_DUMP_PROCESS_NAME, procName); + + mService.mContext.sendBroadcastAsUser(dumpFinishedIntent, + UserHandle.getUserHandleForUid(uid)); + } + + void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, + String reportPackage) { + synchronized (mService) { + if (maxMemSize > 0) { + mMemWatchProcesses.put(processName, uid, new Pair(maxMemSize, reportPackage)); + } else { + if (uid != 0) { + mMemWatchProcesses.remove(processName, uid); + } else { + mMemWatchProcesses.getMap().remove(processName); + } + } + } + } + + /** Clear the currently executing heap dump variables so a new heap dump can be started. */ + private void abortHeapDump(String procName) { + Message msg = mService.mHandler.obtainMessage(ActivityManagerService.ABORT_DUMPHEAP_MSG); + msg.obj = procName; + mService.mHandler.sendMessage(msg); + } + + void handleAbortDumpHeap(String procName) { + if (procName != null) { + synchronized (mService) { + if (procName.equals(mMemWatchDumpProcName)) { + mMemWatchDumpProcName = null; + mMemWatchDumpUri = null; + mMemWatchDumpPid = -1; + mMemWatchDumpUid = -1; + } + } + } + } + + /** @hide */ + private static Uri makeHeapDumpUri(String procName) { + return Uri.parse("content://com.android.shell.heapdump/" + procName + "_javaheap.bin"); + } + + /** + * Schedule PSS collection of a process. + */ + @GuardedBy("mService") + private boolean requestPssLocked(ProcessRecord proc, int procState) { + if (mPendingPssProcesses.contains(proc)) { + return false; + } + if (mPendingPssProcesses.size() == 0) { + final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0) + ? mPssDeferralTime : 0; + if (DEBUG_PSS && deferral > 0) { + Slog.d(TAG_PSS, "requestPssLocked() deferring PSS request by " + + deferral + " ms"); + } + mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, deferral); + } + if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + proc); + proc.pssProcState = procState; + proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE; + mPendingPssProcesses.add(proc); + return true; + } + + /** + * Re-defer a posted PSS collection pass, if one exists. Assumes deferral is + * currently active policy when called. + */ + @GuardedBy("mService") + private void deferPssIfNeededLocked() { + if (mPendingPssProcesses.size() > 0) { + mBgHandler.removeMessages(BgHandler.COLLECT_PSS_BG_MSG); + mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, mPssDeferralTime); + } + } + + private void deferPssForActivityStart() { + if (mPssDeferralTime > 0) { + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Deferring PSS collection for activity start"); + } + deferPssIfNeededLocked(); + mActivityStartingNesting.getAndIncrement(); + mBgHandler.sendEmptyMessageDelayed(BgHandler.STOP_DEFERRING_PSS_MSG, mPssDeferralTime); + } + } + + private void stopDeferPss() { + final int nesting = mActivityStartingNesting.decrementAndGet(); + if (nesting <= 0) { + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "PSS activity start deferral interval ended; now " + + nesting); + } + if (nesting < 0) { + Slog.wtf(TAG, "Activity start nesting undercount!"); + mActivityStartingNesting.incrementAndGet(); + } + } else { + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Still deferring PSS, nesting=" + nesting); + } + } + } + + /** + * Schedule PSS collection of all processes. + */ + @GuardedBy("mService") + void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) { + if (!always) { + if (now < (mLastFullPssTime + + (memLowered ? mService.mConstants.FULL_PSS_LOWERED_INTERVAL + : mService.mConstants.FULL_PSS_MIN_INTERVAL))) { + return; + } + } + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); + } + mLastFullPssTime = now; + mFullPssPending = true; + for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) { + abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker); + } + mPendingPssProcesses.ensureCapacity(mService.mProcessList.getLruSizeLocked()); + mPendingPssProcesses.clear(); + for (int i = mService.mProcessList.getLruSizeLocked() - 1; i >= 0; i--) { + ProcessRecord app = mService.mProcessList.mLruProcesses.get(i); + if (app.thread == null || app.getCurProcState() == PROCESS_STATE_NONEXISTENT) { + continue; + } + if (memLowered || (always && now + > app.lastStateTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) + || now > (app.lastStateTime + ProcessList.PSS_ALL_INTERVAL)) { + app.pssProcState = app.setProcState; + app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL + : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM; + updateNextPssTimeLocked(app.getCurProcState(), app, now, true); + mPendingPssProcesses.add(app); + } + } + if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) { + mBgHandler.sendEmptyMessage(BgHandler.COLLECT_PSS_BG_MSG); + } + } + + void setTestPssMode(boolean enabled) { + synchronized (mService) { + mTestPssMode = enabled; + if (enabled) { + // Whenever we enable the mode, we want to take a snapshot all of current + // process mem use. + requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, true); + } + } + } + + @GuardedBy("mService") + boolean getTestPssModeLocked() { + return mTestPssMode; + } + + @GuardedBy("mService") + int getLastMemoryLevelLocked() { + return mLastMemoryLevel; + } + + @GuardedBy("mService") + boolean isLastMemoryLevelNormal() { + return mLastMemoryLevel <= ProcessStats.ADJ_MEM_FACTOR_NORMAL; + } + + @GuardedBy("mService") + void updateLowRamTimestampLocked(long now) { + mLowRamTimeSinceLastIdle = 0; + if (mLowRamStartTime != 0) { + mLowRamStartTime = now; + } + } + + @GuardedBy("mService") + void setAllowLowerMemLevelLocked(boolean allowLowerMemLevel) { + mAllowLowerMemLevel = allowLowerMemLevel; + } + + @GuardedBy("mService") + boolean updateLowMemStateLocked(int numCached, int numEmpty, int numTrimming) { + final int numOfLru = mService.mProcessList.getLruSizeLocked(); + final long now = SystemClock.uptimeMillis(); + int memFactor; + if (mLowMemDetector != null && mLowMemDetector.isAvailable()) { + memFactor = mLowMemDetector.getMemFactor(); + } else { + // Now determine the memory trimming level of background processes. + // Unfortunately we need to start at the back of the list to do this + // properly. We only do this if the number of background apps we + // are managing to keep around is less than half the maximum we desire; + // if we are keeping a good number around, we'll let them use whatever + // memory they want. + if (numCached <= mService.mConstants.CUR_TRIM_CACHED_PROCESSES + && numEmpty <= mService.mConstants.CUR_TRIM_EMPTY_PROCESSES) { + final int numCachedAndEmpty = numCached + numEmpty; + if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { + memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL; + } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { + memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW; + } else { + memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE; + } + } else { + memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL; + } + } + // We always allow the memory level to go up (better). We only allow it to go + // down if we are in a state where that is allowed, *and* the total number of processes + // has gone down since last time. + if (DEBUG_OOM_ADJ) { + Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel + + " numProcs=" + mService.mProcessList.getLruSizeLocked() + + " last=" + mLastNumProcesses); + } + if (memFactor > mLastMemoryLevel) { + if (!mAllowLowerMemLevel + || mService.mProcessList.getLruSizeLocked() >= mLastNumProcesses) { + memFactor = mLastMemoryLevel; + if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!"); + } + } + if (memFactor != mLastMemoryLevel) { + EventLogTags.writeAmMemFactor(memFactor, mLastMemoryLevel); + FrameworkStatsLog.write(FrameworkStatsLog.MEMORY_FACTOR_STATE_CHANGED, memFactor); + } + mLastMemoryLevel = memFactor; + mLastNumProcesses = mService.mProcessList.getLruSizeLocked(); + boolean allChanged; + int trackerMemFactor; + synchronized (mService.mProcessStats.mLock) { + allChanged = mService.mProcessStats.setMemFactorLocked(memFactor, + mService.mAtmInternal == null || !mService.mAtmInternal.isSleeping(), now); + trackerMemFactor = mService.mProcessStats.getMemFactorLocked(); + } + if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) { + if (mLowRamStartTime == 0) { + mLowRamStartTime = now; + } + int step = 0; + int fgTrimLevel; + switch (memFactor) { + case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; + break; + case ProcessStats.ADJ_MEM_FACTOR_LOW: + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; + break; + default: + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; + break; + } + int factor = numTrimming / 3; + int minFactor = 2; + if (mService.mAtmInternal.getHomeProcess() != null) minFactor++; + if (mService.mAtmInternal.getPreviousProcess() != null) minFactor++; + if (factor < minFactor) factor = minFactor; + int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; + for (int i = numOfLru - 1; i >= 0; i--) { + ProcessRecord app = mService.mProcessList.mLruProcesses.get(i); + if (allChanged || app.procStateChanged) { + mService.setProcessTrackerStateLocked(app, trackerMemFactor, now); + app.procStateChanged = false; + } + if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME + && !app.killedByAm) { + if (app.trimMemoryLevel < curLevel && app.thread != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, + "Trimming memory of " + app.processName + + " to " + curLevel); + } + app.thread.scheduleTrimMemory(curLevel); + } catch (RemoteException e) { + } + } + app.trimMemoryLevel = curLevel; + step++; + if (step >= factor) { + step = 0; + switch (curLevel) { + case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: + curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE; + break; + case ComponentCallbacks2.TRIM_MEMORY_MODERATE: + curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; + break; + } + } + } else if (app.getCurProcState() == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT + && !app.killedByAm) { + if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND + && app.thread != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, + "Trimming memory of heavy-weight " + app.processName + + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); + } + app.thread.scheduleTrimMemory( + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); + } catch (RemoteException e) { + } + } + app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; + } else { + if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + || app.systemNoUi) && app.hasPendingUiClean()) { + // If this application is now in the background and it + // had done UI, then give it the special trim level to + // have it free UI resources. + final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; + if (app.trimMemoryLevel < level && app.thread != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui " + + app.processName + " to " + level); + } + app.thread.scheduleTrimMemory(level); + } catch (RemoteException e) { + } + } + app.setPendingUiClean(false); + } + if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName + + " to " + fgTrimLevel); + } + app.thread.scheduleTrimMemory(fgTrimLevel); + } catch (RemoteException e) { + } + } + app.trimMemoryLevel = fgTrimLevel; + } + } + } else { + if (mLowRamStartTime != 0) { + mLowRamTimeSinceLastIdle += now - mLowRamStartTime; + mLowRamStartTime = 0; + } + for (int i = numOfLru - 1; i >= 0; i--) { + ProcessRecord app = mService.mProcessList.mLruProcesses.get(i); + if (allChanged || app.procStateChanged) { + mService.setProcessTrackerStateLocked(app, trackerMemFactor, now); + app.procStateChanged = false; + } + if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + || app.systemNoUi) && app.hasPendingUiClean()) { + if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN + && app.thread != null) { + try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) { + Slog.v(TAG_OOM_ADJ, + "Trimming memory of ui hidden " + app.processName + + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } + app.thread.scheduleTrimMemory( + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } catch (RemoteException e) { + } + } + app.setPendingUiClean(false); + } + app.trimMemoryLevel = 0; + } + } + return allChanged; + } + + @GuardedBy("mService") + long getLowRamTimeSinceIdleLocked(long now) { + return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0); + } + + @GuardedBy("mService") + private void stopProfilerLocked(ProcessRecord proc, int profileType) { + if (proc == null || proc == mProfileData.getProfileProc()) { + proc = mProfileData.getProfileProc(); + profileType = mProfileType; + clearProfilerLocked(); + } + if (proc == null) { + return; + } + try { + proc.thread.profilerControl(false, null, profileType); + } catch (RemoteException e) { + throw new IllegalStateException("Process disappeared"); + } + } + + @GuardedBy("mService") + void clearProfilerLocked() { + if (mProfileData.getProfilerInfo() != null + && mProfileData.getProfilerInfo().profileFd != null) { + try { + mProfileData.getProfilerInfo().profileFd.close(); + } catch (IOException e) { + } + } + mProfileData.setProfileApp(null); + mProfileData.setProfileProc(null); + mProfileData.setProfilerInfo(null); + } + + @GuardedBy("mService") + void clearProfilerLocked(ProcessRecord app) { + if (mProfileData.getProfileProc() == null + || mProfileData.getProfilerInfo() == null + || mProfileData.getProfileProc() != app) { + return; + } + clearProfilerLocked(); + } + + @GuardedBy("mService") + boolean profileControlLocked(ProcessRecord proc, boolean start, + ProfilerInfo profilerInfo, int profileType) { + try { + if (start) { + stopProfilerLocked(null, 0); + mService.setProfileApp(proc.info, proc.processName, profilerInfo); + mProfileData.setProfileProc(proc); + mProfileType = profileType; + ParcelFileDescriptor fd = profilerInfo.profileFd; + try { + fd = fd.dup(); + } catch (IOException e) { + fd = null; + } + profilerInfo.profileFd = fd; + proc.thread.profilerControl(start, profilerInfo, profileType); + fd = null; + try { + mProfileData.getProfilerInfo().profileFd.close(); + } catch (IOException e) { + } + mProfileData.getProfilerInfo().profileFd = null; + + if (proc.pid == mService.MY_PID) { + // When profiling the system server itself, avoid closing the file + // descriptor, as profilerControl will not create a copy. + // Note: it is also not correct to just set profileFd to null, as the + // whole ProfilerInfo instance is passed down! + profilerInfo = null; + } + } else { + stopProfilerLocked(proc, profileType); + if (profilerInfo != null && profilerInfo.profileFd != null) { + try { + profilerInfo.profileFd.close(); + } catch (IOException e) { + } + } + } + + return true; + } catch (RemoteException e) { + throw new IllegalStateException("Process disappeared"); + } finally { + if (profilerInfo != null && profilerInfo.profileFd != null) { + try { + profilerInfo.profileFd.close(); + } catch (IOException e) { + } + } + } + } + + @GuardedBy("mService") + void setProfileAppLocked(String processName, ProfilerInfo profilerInfo) { + mProfileData.setProfileApp(processName); + + if (mProfileData.getProfilerInfo() != null) { + if (mProfileData.getProfilerInfo().profileFd != null) { + try { + mProfileData.getProfilerInfo().profileFd.close(); + } catch (IOException e) { + } + } + } + mProfileData.setProfilerInfo(new ProfilerInfo(profilerInfo)); + mProfileType = 0; + } + + @GuardedBy("mService") + void setProfileProcLocked(ProcessRecord proc) { + mProfileData.setProfileProc(proc); + } + + @GuardedBy("mService") + void setAgentAppLocked(@NonNull String packageName, @Nullable String agent) { + if (agent == null) { + if (mAppAgentMap != null) { + mAppAgentMap.remove(packageName); + if (mAppAgentMap.isEmpty()) { + mAppAgentMap = null; + } + } + } else { + if (mAppAgentMap == null) { + mAppAgentMap = new HashMap<>(); + } + if (mAppAgentMap.size() >= 100) { + // Limit the size of the map, to avoid OOMEs. + Slog.e(TAG, "App agent map has too many entries, cannot add " + packageName + + "/" + agent); + return; + } + mAppAgentMap.put(packageName, agent); + } + } + + @GuardedBy("mService") + void updateCpuStatsLocked() { + final long now = SystemClock.uptimeMillis(); + if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) { + return; + } + if (mProcessCpuMutexFree.compareAndSet(true, false)) { + synchronized (mProcessCpuThread) { + mProcessCpuThread.notify(); + } + } + } + + void updateCpuStatsNow() { + synchronized (mProcessCpuTracker) { + mProcessCpuMutexFree.set(false); + final long now = SystemClock.uptimeMillis(); + boolean haveNewCpuStats = false; + + if (MONITOR_CPU_USAGE + && mLastCpuTime.get() < (now - MONITOR_CPU_MIN_TIME)) { + mLastCpuTime.set(now); + mProcessCpuTracker.update(); + if (mProcessCpuTracker.hasGoodLastStats()) { + haveNewCpuStats = true; + //Slog.i(TAG, mProcessCpu.printCurrentState()); + //Slog.i(TAG, "Total CPU usage: " + // + mProcessCpu.getTotalCpuPercent() + "%"); + + // Slog the cpu usage if the property is set. + if ("true".equals(SystemProperties.get("events.cpu"))) { + int user = mProcessCpuTracker.getLastUserTime(); + int system = mProcessCpuTracker.getLastSystemTime(); + int iowait = mProcessCpuTracker.getLastIoWaitTime(); + int irq = mProcessCpuTracker.getLastIrqTime(); + int softIrq = mProcessCpuTracker.getLastSoftIrqTime(); + int idle = mProcessCpuTracker.getLastIdleTime(); + + int total = user + system + iowait + irq + softIrq + idle; + if (total == 0) total = 1; + + EventLogTags.writeCpu( + ((user + system + iowait + irq + softIrq) * 100) / total, + (user * 100) / total, + (system * 100) / total, + (iowait * 100) / total, + (irq * 100) / total, + (softIrq * 100) / total); + } + } + } + + final BatteryStatsImpl bstats = mService.mBatteryStatsService.getActiveStatistics(); + synchronized (bstats) { + if (haveNewCpuStats) { + if (bstats.startAddingCpuLocked()) { + int totalUTime = 0; + int totalSTime = 0; + final int statsCount = mProcessCpuTracker.countStats(); + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + synchronized (mService.mPidsSelfLocked) { + for (int i = 0; i < statsCount; i++) { + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); + if (!st.working) { + continue; + } + ProcessRecord pr = mService.mPidsSelfLocked.get(st.pid); + totalUTime += st.rel_utime; + totalSTime += st.rel_stime; + if (pr != null) { + BatteryStatsImpl.Uid.Proc ps = pr.curProcBatteryStats; + if (ps == null || !ps.isActive()) { + pr.curProcBatteryStats = ps = bstats.getProcessStatsLocked( + pr.info.uid, pr.processName, + elapsedRealtime, uptime); + } + ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + pr.curCpuTime += st.rel_utime + st.rel_stime; + if (pr.lastCpuTime == 0) { + pr.lastCpuTime = pr.curCpuTime; + } + } else { + BatteryStatsImpl.Uid.Proc ps = st.batteryStats; + if (ps == null || !ps.isActive()) { + st.batteryStats = ps = bstats.getProcessStatsLocked( + bstats.mapUid(st.uid), st.name, + elapsedRealtime, uptime); + } + ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + } + } + } + + final int userTime = mProcessCpuTracker.getLastUserTime(); + final int systemTime = mProcessCpuTracker.getLastSystemTime(); + final int iowaitTime = mProcessCpuTracker.getLastIoWaitTime(); + final int irqTime = mProcessCpuTracker.getLastIrqTime(); + final int softIrqTime = mProcessCpuTracker.getLastSoftIrqTime(); + final int idleTime = mProcessCpuTracker.getLastIdleTime(); + bstats.finishAddingCpuLocked(totalUTime, totalSTime, userTime, + systemTime, iowaitTime, irqTime, softIrqTime, idleTime); + } + } + + if (mLastWriteTime < (now - BATTERY_STATS_TIME)) { + mLastWriteTime = now; + mService.mBatteryStatsService.scheduleWriteToDisk(); + } + } + } + } + + long getCpuTimeForPid(int pid) { + synchronized (mProcessCpuTracker) { + return mProcessCpuTracker.getCpuTimeForPid(pid); + } + } + + List<ProcessCpuTracker.Stats> getCpuStats(Predicate<ProcessCpuTracker.Stats> predicate) { + synchronized (mProcessCpuTracker) { + return mProcessCpuTracker.getStats(st -> predicate.test(st)); + } + } + + void forAllCpuStats(Consumer<ProcessCpuTracker.Stats> consumer) { + synchronized (mProcessCpuTracker) { + final int numOfStats = mProcessCpuTracker.countStats(); + for (int i = 0; i < numOfStats; i++) { + consumer.accept(mProcessCpuTracker.getStats(i)); + } + } + } + + private class ProcessCpuThread extends Thread { + ProcessCpuThread(String name) { + super(name); + } + + @Override + public void run() { + synchronized (mProcessCpuTracker) { + mProcessCpuInitLatch.countDown(); + mProcessCpuTracker.init(); + } + while (true) { + try { + try { + synchronized (this) { + final long now = SystemClock.uptimeMillis(); + long nextCpuDelay = (mLastCpuTime.get() + MONITOR_CPU_MAX_TIME) - now; + long nextWriteDelay = (mLastWriteTime + BATTERY_STATS_TIME) - now; + //Slog.i(TAG, "Cpu delay=" + nextCpuDelay + // + ", write delay=" + nextWriteDelay); + if (nextWriteDelay < nextCpuDelay) { + nextCpuDelay = nextWriteDelay; + } + if (nextCpuDelay > 0) { + mProcessCpuMutexFree.set(true); + this.wait(nextCpuDelay); + } + } + } catch (InterruptedException e) { + } + updateCpuStatsNow(); + } catch (Exception e) { + Slog.e(TAG, "Unexpected exception collecting process stats", e); + } + } + } + } + + class CpuBinder extends Binder { + private final PriorityDump.PriorityDumper mPriorityDumper = + new PriorityDump.PriorityDumper() { + @Override + public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(mService.mContext, "cpuinfo", pw)) { + return; + } + synchronized (mProcessCpuTracker) { + if (asProto) { + mProcessCpuTracker.dumpProto(fd); + return; + } + pw.print(mProcessCpuTracker.printCurrentLoad()); + pw.print(mProcessCpuTracker.printCurrentState( + SystemClock.uptimeMillis())); + } + } + }; + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + PriorityDump.dump(mPriorityDumper, fd, pw, args); + } + } + + void setCpuInfoService() { + if (MONITOR_CPU_USAGE) { + ServiceManager.addService("cpuinfo", new CpuBinder(), + /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); + } + } + + AppProfiler(ActivityManagerService service, Looper bgLooper, LowMemDetector detector) { + mService = service; + mBgHandler = new BgHandler(bgLooper); + mLowMemDetector = detector; + mProcessCpuThread = new ProcessCpuThread("CpuTracker"); + } + + void retrieveSettings() { + final long pssDeferralMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + ACTIVITY_START_PSS_DEFER_CONFIG, 0L); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + ActivityThread.currentApplication().getMainExecutor(), + mPssDelayConfigListener); + mPssDeferralTime = pssDeferralMs; + } + + void onActivityManagerInternalAdded() { + mProcessCpuThread.start(); + // Wait for the synchronized block started in mProcessCpuThread, + // so that any other access to mProcessCpuTracker from main thread + // will be blocked during mProcessCpuTracker initialization. + try { + mProcessCpuInitLatch.await(); + } catch (InterruptedException e) { + Slog.wtf(TAG, "Interrupted wait during start", e); + Thread.currentThread().interrupt(); + throw new IllegalStateException("Interrupted wait during start"); + } + } + + void onActivityLaunched() { + // This is safe to force to the head of the queue because it relies only + // on refcounting to track begin/end of deferrals, not on actual + // message ordering. We don't care *what* activity is being + // launched; only that we're doing so. + if (mPssDeferralTime > 0) { + final Message msg = mBgHandler.obtainMessage(BgHandler.DEFER_PSS_MSG); + mBgHandler.sendMessageAtFrontOfQueue(msg); + } + } + + @GuardedBy("mService") + ProfilerInfo setupProfilerInfoLocked(@NonNull IApplicationThread thread, ProcessRecord app, + ActiveInstrumentation instr) throws IOException, RemoteException { + ProfilerInfo profilerInfo = null; + String preBindAgent = null; + final String processName = app.processName; + if (mProfileData.getProfileApp() != null + && mProfileData.getProfileApp().equals(processName)) { + mProfileData.setProfileProc(app); + if (mProfileData.getProfilerInfo() != null) { + // Send a profiler info object to the app if either a file is given, or + // an agent should be loaded at bind-time. + boolean needsInfo = mProfileData.getProfilerInfo().profileFile != null + || mProfileData.getProfilerInfo().attachAgentDuringBind; + profilerInfo = needsInfo + ? new ProfilerInfo(mProfileData.getProfilerInfo()) : null; + if (mProfileData.getProfilerInfo().agent != null) { + preBindAgent = mProfileData.getProfilerInfo().agent; + } + } + } else if (instr != null && instr.mProfileFile != null) { + profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false, + null, false); + } + if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) { + // We need to do a debuggable check here. See setAgentApp for why the check is + // postponed to here. + if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + String agent = mAppAgentMap.get(processName); + // Do not overwrite already requested agent. + if (profilerInfo == null) { + profilerInfo = new ProfilerInfo(null, null, 0, false, false, + mAppAgentMap.get(processName), true); + } else if (profilerInfo.agent == null) { + profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true); + } + } + } + + if (profilerInfo != null && profilerInfo.profileFd != null) { + profilerInfo.profileFd = profilerInfo.profileFd.dup(); + if (TextUtils.equals(mProfileData.getProfileApp(), processName) + && mProfileData.getProfilerInfo() != null) { + clearProfilerLocked(); + } + } + + // Check if this is a secondary process that should be incorporated into some + // currently active instrumentation. (Note we do this AFTER all of the profiling + // stuff above because profiling can currently happen only in the primary + // instrumentation process.) + if (mService.mActiveInstrumentation.size() > 0 && instr == null) { + for (int i = mService.mActiveInstrumentation.size() - 1; + i >= 0 && app.getActiveInstrumentation() == null; i--) { + ActiveInstrumentation aInstr = mService.mActiveInstrumentation.get(i); + if (!aInstr.mFinished && aInstr.mTargetInfo.uid == app.uid) { + if (aInstr.mTargetProcesses.length == 0) { + // This is the wildcard mode, where every process brought up for + // the target instrumentation should be included. + if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) { + app.setActiveInstrumentation(aInstr); + aInstr.mRunningProcesses.add(app); + } + } else { + for (String proc : aInstr.mTargetProcesses) { + if (proc.equals(app.processName)) { + app.setActiveInstrumentation(aInstr); + aInstr.mRunningProcesses.add(app); + break; + } + } + } + } + } + } + + // If we were asked to attach an agent on startup, do so now, before we're binding + // application code. + if (preBindAgent != null) { + thread.attachAgent(preBindAgent); + } + if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + thread.attachStartupAgents(app.info.dataDir); + } + return profilerInfo; + } + + @GuardedBy("mService") + void onCleanupApplicationRecordLocked(ProcessRecord app) { + mPendingPssProcesses.remove(app); + abortNextPssTime(app.procStateMemTracker); + } + + @GuardedBy("mService") + void onAppDiedLocked(ProcessRecord app) { + if (mProfileData.getProfileProc() == app) { + clearProfilerLocked(); + } + } + + @GuardedBy("mService") + boolean dumpMemWatchProcessesLocked(PrintWriter pw, boolean needSep) { + if (mMemWatchProcesses.getMap().size() > 0) { + pw.println(" Mem watch processes:"); + final ArrayMap<String, SparseArray<Pair<Long, String>>> procs = + mMemWatchProcesses.getMap(); + for (int i = procs.size() - 1; i >= 0; i--) { + final String proc = procs.keyAt(i); + final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); + for (int j = uids.size() - 1; j >= 0; j--) { + if (needSep) { + pw.println(); + needSep = false; + } + StringBuilder sb = new StringBuilder(); + sb.append(" ").append(proc).append('/'); + UserHandle.formatUid(sb, uids.keyAt(j)); + Pair<Long, String> val = uids.valueAt(j); + sb.append(": "); DebugUtils.sizeValueToString(val.first, sb); + if (val.second != null) { + sb.append(", report to ").append(val.second); + } + pw.println(sb.toString()); + } + } + pw.print(" mMemWatchDumpProcName="); pw.println(mMemWatchDumpProcName); + pw.print(" mMemWatchDumpUri="); pw.println(mMemWatchDumpUri); + pw.print(" mMemWatchDumpPid="); pw.println(mMemWatchDumpPid); + pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid); + pw.print(" mMemWatchIsUserInitiated="); pw.println(mMemWatchIsUserInitiated); + } + return needSep; + } + + @GuardedBy("mService") + boolean dumpProfileDataLocked(PrintWriter pw, String dumpPackage, boolean needSep) { + if (mProfileData.getProfileApp() != null || mProfileData.getProfileProc() != null + || (mProfileData.getProfilerInfo() != null + && (mProfileData.getProfilerInfo().profileFile != null + || mProfileData.getProfilerInfo().profileFd != null))) { + if (dumpPackage == null || dumpPackage.equals(mProfileData.getProfileApp())) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mProfileApp=" + mProfileData.getProfileApp() + + " mProfileProc=" + mProfileData.getProfileProc()); + if (mProfileData.getProfilerInfo() != null) { + pw.println(" mProfileFile=" + mProfileData.getProfilerInfo().profileFile + + " mProfileFd=" + mProfileData.getProfilerInfo().profileFd); + pw.println(" mSamplingInterval=" + + mProfileData.getProfilerInfo().samplingInterval + + " mAutoStopProfiler=" + + mProfileData.getProfilerInfo().autoStopProfiler + + " mStreamingOutput=" + + mProfileData.getProfilerInfo().streamingOutput); + pw.println(" mProfileType=" + mProfileType); + } + } + } + return needSep; + } + + @GuardedBy("mService") + void dumpLastMemoryLevelLocked(PrintWriter pw) { + switch (mLastMemoryLevel) { + case ProcessStats.ADJ_MEM_FACTOR_NORMAL: + pw.println("normal)"); + break; + case ProcessStats.ADJ_MEM_FACTOR_MODERATE: + pw.println("moderate)"); + break; + case ProcessStats.ADJ_MEM_FACTOR_LOW: + pw.println("low)"); + break; + case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: + pw.println("critical)"); + break; + default: + pw.print(mLastMemoryLevel); + pw.println(")"); + break; + } + } + + @GuardedBy("mService") + void dumpMemoryLevelsLocked(PrintWriter pw) { + pw.println(" mAllowLowerMemLevel=" + mAllowLowerMemLevel + + " mLastMemoryLevel=" + mLastMemoryLevel + + " mLastNumProcesses=" + mLastNumProcesses); + } + + @GuardedBy("mService") + void writeMemWatchProcessToProtoLocked(ProtoOutputStream proto) { + if (mMemWatchProcesses.getMap().size() > 0) { + final long token = proto.start( + ActivityManagerServiceDumpProcessesProto.MEM_WATCH_PROCESSES); + ArrayMap<String, SparseArray<Pair<Long, String>>> procs = mMemWatchProcesses.getMap(); + for (int i = 0; i < procs.size(); i++) { + final String proc = procs.keyAt(i); + final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); + final long ptoken = proto.start( + ActivityManagerServiceDumpProcessesProto.MemWatchProcess.PROCS); + proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Process.NAME, + proc); + for (int j = uids.size() - 1; j >= 0; j--) { + final long utoken = proto.start(ActivityManagerServiceDumpProcessesProto + .MemWatchProcess.Process.MEM_STATS); + Pair<Long, String> val = uids.valueAt(j); + proto.write(ActivityManagerServiceDumpProcessesProto + .MemWatchProcess.Process.MemStats.UID, uids.keyAt(j)); + proto.write(ActivityManagerServiceDumpProcessesProto + .MemWatchProcess.Process.MemStats.SIZE, + DebugUtils.sizeValueToString(val.first, new StringBuilder())); + proto.write(ActivityManagerServiceDumpProcessesProto + .MemWatchProcess.Process.MemStats.REPORT_TO, val.second); + proto.end(utoken); + } + proto.end(ptoken); + } + + final long dtoken = proto.start( + ActivityManagerServiceDumpProcessesProto.MemWatchProcess.DUMP); + proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.PROC_NAME, + mMemWatchDumpProcName); + proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.URI, + mMemWatchDumpUri.toString()); + proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.PID, + mMemWatchDumpPid); + proto.write(ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.UID, + mMemWatchDumpUid); + proto.write( + ActivityManagerServiceDumpProcessesProto.MemWatchProcess.Dump.IS_USER_INITIATED, + mMemWatchIsUserInitiated); + proto.end(dtoken); + + proto.end(token); + } + } + + @GuardedBy("mService") + void writeProfileDataToProtoLocked(ProtoOutputStream proto, String dumpPackage) { + if (mProfileData.getProfileApp() != null || mProfileData.getProfileProc() != null + || (mProfileData.getProfilerInfo() != null + && (mProfileData.getProfilerInfo().profileFile != null + || mProfileData.getProfilerInfo().profileFd != null))) { + if (dumpPackage == null || dumpPackage.equals(mProfileData.getProfileApp())) { + final long token = proto.start(ActivityManagerServiceDumpProcessesProto.PROFILE); + proto.write(ActivityManagerServiceDumpProcessesProto.Profile.APP_NAME, + mProfileData.getProfileApp()); + mProfileData.getProfileProc().dumpDebug(proto, + ActivityManagerServiceDumpProcessesProto.Profile.PROC); + if (mProfileData.getProfilerInfo() != null) { + mProfileData.getProfilerInfo().dumpDebug(proto, + ActivityManagerServiceDumpProcessesProto.Profile.INFO); + proto.write(ActivityManagerServiceDumpProcessesProto.Profile.TYPE, + mProfileType); + } + proto.end(token); + } + } + } + + @GuardedBy("mService") + void writeMemoryLevelsToProtoLocked(ProtoOutputStream proto) { + proto.write(ActivityManagerServiceDumpProcessesProto.ALLOW_LOWER_MEM_LEVEL, + mAllowLowerMemLevel); + proto.write(ActivityManagerServiceDumpProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel); + proto.write(ActivityManagerServiceDumpProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses); + } + + void printCurrentCpuState(StringBuilder report, long time) { + synchronized (mProcessCpuTracker) { + report.append(mProcessCpuTracker.printCurrentState(time)); + } + } +} diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 6948f9061038..93105daa6c5d 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -964,7 +964,7 @@ public final class BroadcastQueue { + mParallelBroadcasts.size() + " parallel broadcasts; " + mDispatcher.describeStateLocked()); - mService.updateCpuStats(); + mService.updateCpuStatsLocked(); if (fromMsg) { mBroadcastsScheduled = false; diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index fc330313373e..c313eb5760eb 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -66,9 +66,9 @@ import static com.android.server.am.ActivityManagerService.TAG_BACKUP; import static com.android.server.am.ActivityManagerService.TAG_LRU; import static com.android.server.am.ActivityManagerService.TAG_OOM_ADJ; import static com.android.server.am.ActivityManagerService.TAG_PROCESS_OBSERVERS; -import static com.android.server.am.ActivityManagerService.TAG_PSS; import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST; +import static com.android.server.am.AppProfiler.TAG_PSS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import android.app.ActivityManager; @@ -83,7 +83,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; -import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.PowerManagerInternal; @@ -102,7 +101,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.procstats.ProcessStats; import com.android.internal.compat.IPlatformCompat; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -819,7 +817,7 @@ public final class OomAdjuster { } if (allChanged) { - mService.requestPssAllProcsLocked(now, false, + mService.mAppProfiler.requestPssAllProcsLocked(now, false, mService.mProcessStats.isMemFactorLowered()); } @@ -1075,7 +1073,7 @@ public final class OomAdjuster { mProcessList.incrementProcStateSeqAndNotifyAppsLocked(activeUids); - return mService.updateLowMemStateLocked(numCached, numEmpty, numTrimming); + return mService.mAppProfiler.updateLowMemStateLocked(numCached, numEmpty, numTrimming); } private void updateAppUidRecLocked(ProcessRecord app) { @@ -2176,7 +2174,7 @@ public final class OomAdjuster { // normally be a B service, but if we are low on RAM and it // is large we want to force it down since we would prefer to // keep launcher over it. - if (mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL + if (!mService.mAppProfiler.isLastMemoryLevelNormal() && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { app.serviceHighRam = true; app.serviceb = true; @@ -2493,45 +2491,16 @@ public final class OomAdjuster { } if (app.setProcState == PROCESS_STATE_NONEXISTENT || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) { - if (false && mService.mTestPssMode - && app.setProcState >= 0 && app.lastStateTime <= (now-200)) { - // Experimental code to more aggressively collect pss while - // running test... the problem is that this tends to collect - // the data right when a process is transitioning between process - // states, which will tend to give noisy data. - long start = SystemClock.uptimeMillis(); - long startTime = SystemClock.currentThreadTimeMillis(); - long pss = Debug.getPss(app.pid, mTmpLong, null); - long endTime = SystemClock.currentThreadTimeMillis(); - mService.recordPssSampleLocked(app, app.getCurProcState(), pss, - mTmpLong[0], mTmpLong[1], mTmpLong[2], - ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now); - mService.mPendingPssProcesses.remove(app); - Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState - + " to " + app.getCurProcState() + ": " - + (SystemClock.uptimeMillis()-start) + "ms"); - } app.lastStateTime = now; - app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), - app.procStateMemTracker, mService.mTestPssMode, - mService.mAtmInternal.isSleeping(), now); - if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from " - + ProcessList.makeProcStateString(app.setProcState) + " to " - + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in " - + (app.nextPssTime-now) + ": " + app); - } else { - if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL) - && now > (app.lastStateTime+ProcessList.minTimeFromStateChange( - mService.mTestPssMode)))) { - if (mService.requestPssLocked(app, app.setProcState)) { - app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(), - app.procStateMemTracker, mService.mTestPssMode, - mService.mAtmInternal.isSleeping(), now); - } - } else if (false && DEBUG_PSS) { - Slog.d(TAG_PSS, - "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now)); + mService.mAppProfiler.updateNextPssTimeLocked(app.getCurProcState(), app, now, true); + if (DEBUG_PSS) { + Slog.d(TAG_PSS, "Process state change from " + + ProcessList.makeProcStateString(app.setProcState) + " to " + + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in " + + (app.nextPssTime - now) + ": " + app); } + } else { + mService.mAppProfiler.updateNextPssTimeLocked(app.getCurProcState(), app, now, false); } if (app.setProcState != app.getCurProcState()) { if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 87898d80bd46..ced2f0f5ebc4 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -48,8 +48,8 @@ import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; import static com.android.server.am.ActivityManagerService.TAG_LRU; import static com.android.server.am.ActivityManagerService.TAG_NETWORK; import static com.android.server.am.ActivityManagerService.TAG_PROCESSES; -import static com.android.server.am.ActivityManagerService.TAG_PSS; import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS; +import static com.android.server.am.AppProfiler.TAG_PSS; import android.app.ActivityManager; import android.app.ActivityThread; @@ -1323,19 +1323,6 @@ public final class ProcessList { return test ? PSS_TEST_MIN_TIME_FROM_STATE_CHANGE : PSS_MIN_TIME_FROM_STATE_CHANGE; } - public static void commitNextPssTime(ProcStateMemTracker tracker) { - if (tracker.mPendingMemState >= 0) { - tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState; - tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor; - tracker.mTotalHighestMem = tracker.mPendingHighestMemState; - tracker.mPendingMemState = -1; - } - } - - public static void abortNextPssTime(ProcStateMemTracker tracker) { - tracker.mPendingMemState = -1; - } - public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, boolean sleeping, long now) { boolean first; @@ -1547,7 +1534,7 @@ public final class ProcessList { ApplicationExitInfo.SUBREASON_LARGE_CACHED, true); } else if (proc != null && !keepIfLarge - && mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL + && !mService.mAppProfiler.isLastMemoryLevelNormal() && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { if (DEBUG_PSS) Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + proc .lastCachedPss); @@ -1772,7 +1759,7 @@ public final class ProcessList { mService.mProcessesOnHold.remove(app); checkSlow(startTime, "startProcess: starting to update cpu stats"); - mService.updateCpuStats(); + mService.updateCpuStatsLocked(); checkSlow(startTime, "startProcess: done updating cpu stats"); try { @@ -2422,7 +2409,8 @@ public final class ProcessList { ProcessList.killProcessGroup(app.uid, app.pid); checkSlow(startTime, "startProcess: done killing old proc"); - if (!app.killed || mService.mLastMemoryLevel <= ProcessStats.ADJ_MEM_FACTOR_NORMAL + if (!app.killed + || mService.mAppProfiler.isLastMemoryLevelNormal() || app.setProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY || app.lastCachedPss < getCachedRestoreThresholdKb()) { // Throw a wtf if it's not killed, or killed but not because the system was in diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 14bc6cbed7d7..f1e12c55df18 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1422,12 +1422,7 @@ class ProcessRecord implements WindowProcessListener { @Override public void clearProfilerIfNeeded() { synchronized (mService) { - if (mService.mProfileData.getProfileProc() == null - || mService.mProfileData.getProfilerInfo() == null - || mService.mProfileData.getProfileProc() != this) { - return; - } - mService.clearProfilerLocked(); + mService.mAppProfiler.clearProfilerLocked(); } } @@ -1483,7 +1478,7 @@ class ProcessRecord implements WindowProcessListener { */ @Override public long getCpuTime() { - return mService.mProcessCpuTracker.getCpuTimeForPid(pid); + return mService.mAppProfiler.getCpuTimeForPid(pid); } @Override @@ -1492,7 +1487,7 @@ class ProcessRecord implements WindowProcessListener { synchronized (mService) { waitingToKill = null; if (setProfileProc) { - mService.mProfileData.setProfileProc(this); + mService.mAppProfiler.setProfileProcLocked(this); } if (packageName != null) { addPackage(packageName, versionCode, mService.mProcessStats); @@ -1566,7 +1561,7 @@ class ProcessRecord implements WindowProcessListener { /** Non-private access is for tests only. */ @VisibleForTesting boolean isMonitorCpuUsage() { - return mService.MONITOR_CPU_USAGE; + return mService.mAppProfiler.MONITOR_CPU_USAGE; } void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo, @@ -1701,9 +1696,7 @@ class ProcessRecord implements WindowProcessListener { if (isMonitorCpuUsage()) { mService.updateCpuStatsNow(); - synchronized (mService.mProcessCpuTracker) { - report.append(mService.mProcessCpuTracker.printCurrentState(anrTime)); - } + mService.mAppProfiler.printCurrentCpuState(report, anrTime); info.append(processCpuTracker.printCurrentLoad()); info.append(report); } diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index 4e8c386a3c66..d925defdbc73 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -314,7 +314,8 @@ public final class ProcessStatsService extends IProcessStats.Stub { private void scheduleRequestPssAllProcs(boolean always, boolean memLowered) { mAm.mHandler.post(() -> { synchronized (mAm) { - mAm.requestPssAllProcsLocked(SystemClock.uptimeMillis(), always, memLowered); + mAm.mAppProfiler.requestPssAllProcsLocked( + SystemClock.uptimeMillis(), always, memLowered); } }); } @@ -1056,17 +1057,13 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } } else if ("--start-testing".equals(arg)) { - synchronized (mAm) { - mAm.setTestPssMode(true); - pw.println("Started high frequency sampling."); - quit = true; - } + mAm.mAppProfiler.setTestPssMode(true); + pw.println("Started high frequency sampling."); + quit = true; } else if ("--stop-testing".equals(arg)) { - synchronized (mAm) { - mAm.setTestPssMode(false); - pw.println("Stopped high frequency sampling."); - quit = true; - } + mAm.mAppProfiler.setTestPssMode(false); + pw.println("Stopped high frequency sampling."); + quit = true; } else if ("--pretend-screen-on".equals(arg)) { synchronized (mLock) { mInjectedScreenState = true; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index b56b09d707af..06f44b1bd388 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -39,6 +39,7 @@ import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; import static android.app.AppOpsManager.OpEventProxyInfo; import static android.app.AppOpsManager.RestrictionBypass; import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING; @@ -3428,7 +3429,19 @@ public class AppOpsService extends IAppOpsService.Stub { String resolvedPackageName = resolvePackageName(uid, packageName); if (resolvedPackageName == null) { - return AppOpsManager.MODE_IGNORED; + return AppOpsManager.MODE_IGNORED; + } + + // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution + // purposes and not as a check, also make sure that the caller is allowed to access + // the data gated by OP_RECORD_AUDIO. + // + // TODO: Revert this change before Android 12. + if (code == OP_RECORD_AUDIO_HOTWORD) { + int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + if (result != AppOpsManager.MODE_ALLOWED) { + return result; + } } RestrictionBypass bypass; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java index d2c35feccc11..1613d3bb4e8a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java @@ -282,8 +282,10 @@ class Face10 implements IHwBinder.DeathRecipient { @NonNull LockoutResetDispatcher lockoutResetDispatcher) { final boolean supportsSelfIllumination = context.getResources() .getBoolean(R.bool.config_faceAuthSupportsSelfIllumination); + final int maxTemplatesAllowed = context.getResources() + .getInteger(R.integer.config_faceMaxTemplatesPerUser); mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */, - supportsSelfIllumination); + supportsSelfIllumination, maxTemplatesAllowed); mContext = context; mSensorId = sensorId; mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java index 3754bd748781..7e62329a168a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java @@ -45,6 +45,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Surface; +import com.android.internal.R; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.Utils; import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto; @@ -338,8 +339,10 @@ class Fingerprint21 implements IHwBinder.DeathRecipient { : FingerprintSensorProperties.TYPE_REAR; // resetLockout is controlled by the framework, so hardwareAuthToken is not required final boolean resetLockoutRequiresHardwareAuthToken = false; + final int maxTemplatesAllowed = mContext.getResources() + .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser); mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType, - resetLockoutRequiresHardwareAuthToken); + resetLockoutRequiresHardwareAuthToken, maxTemplatesAllowed); } static Fingerprint21 newInstance(@NonNull Context context, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java index 1a6ad46fce67..7cab2b889eaa 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java @@ -35,6 +35,7 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseBooleanArray; +import com.android.internal.R; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.ClientMonitor; @@ -408,8 +409,11 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage mHandler = handler; // resetLockout is controlled by the framework, so hardwareAuthToken is not required final boolean resetLockoutRequiresHardwareAuthToken = false; + final int maxTemplatesAllowed = mContext.getResources() + .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser); mSensorProperties = new FingerprintSensorProperties(sensorId, - FingerprintSensorProperties.TYPE_UDFPS, resetLockoutRequiresHardwareAuthToken); + FingerprintSensorProperties.TYPE_UDFPS, resetLockoutRequiresHardwareAuthToken, + maxTemplatesAllowed); mMockHalResultController = controller; mUserHasTrust = new SparseBooleanArray(); mTrustManager = context.getSystemService(TrustManager.class); diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index d9b5b6d41c11..f15e22f92cad 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -101,7 +101,7 @@ class MediaSessionStack { if (mMediaButtonSession == record) { // When the media button session is removed, nullify the media button session and do not // search for the alternative media session within the app. It's because the alternative - // media session might be a dummy which isn't able to handle the media key events. + // media session might be a fake which isn't able to handle the media key events. // TODO(b/154456172): Make this decision unaltered by non-media app's playback. updateMediaButtonSession(null); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index bae02ac75f0a..cff4f07fa9d8 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -469,6 +469,69 @@ public final class NotificationRecord { pw.println(prefix + "key=" + getSbn().getKey()); pw.println(prefix + "seen=" + mStats.hasSeen()); pw.println(prefix + "groupKey=" + getGroupKey()); + pw.println(prefix + "notification="); + dumpNotification(pw, prefix + prefix, notification, redact); + pw.println(prefix + "publicNotification="); + dumpNotification(pw, prefix + prefix, notification.publicVersion, redact); + pw.println(prefix + "stats=" + stats.toString()); + pw.println(prefix + "mContactAffinity=" + mContactAffinity); + pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive); + pw.println(prefix + "mPackagePriority=" + mPackagePriority); + pw.println(prefix + "mPackageVisibility=" + mPackageVisibility); + pw.println(prefix + "mSystemImportance=" + + NotificationListenerService.Ranking.importanceToString(mSystemImportance)); + pw.println(prefix + "mAsstImportance=" + + NotificationListenerService.Ranking.importanceToString(mAssistantImportance)); + pw.println(prefix + "mImportance=" + + NotificationListenerService.Ranking.importanceToString(mImportance)); + pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation()); + pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked); + pw.println(prefix + "mIntercept=" + mIntercept); + pw.println(prefix + "mHidden==" + mHidden); + pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey); + pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs); + pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs); + pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs); + pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs); + pw.println(prefix + "mInterruptionTimeMs=" + mInterruptionTimeMs); + pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects); + if (mPreChannelsNotification) { + pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x", + notification.defaults, notification.flags)); + pw.println(prefix + "n.sound=" + notification.sound); + pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType); + pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes); + pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", + notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); + pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate)); + } + pw.println(prefix + "mSound= " + mSound); + pw.println(prefix + "mVibration= " + mVibration); + pw.println(prefix + "mAttributes= " + mAttributes); + pw.println(prefix + "mLight= " + mLight); + pw.println(prefix + "mShowBadge=" + mShowBadge); + pw.println(prefix + "mColorized=" + notification.isColorized()); + pw.println(prefix + "mAllowBubble=" + mAllowBubble); + pw.println(prefix + "isBubble=" + notification.isBubbleNotification()); + pw.println(prefix + "mIsInterruptive=" + mIsInterruptive); + pw.println(prefix + "effectiveNotificationChannel=" + getChannel()); + if (getPeopleOverride() != null) { + pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride())); + } + if (getSnoozeCriteria() != null) { + pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria())); + } + pw.println(prefix + "mAdjustments=" + mAdjustments); + pw.println(prefix + "shortcut=" + notification.getShortcutId() + + " found valid? " + (mShortcutInfo != null)); + } + + private void dumpNotification(PrintWriter pw, String prefix, Notification notification, + boolean redact) { + if (notification == null) { + pw.println(prefix + "None"); + return; + } pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent); pw.println(prefix + "contentIntent=" + notification.contentIntent); pw.println(prefix + "deleteIntent=" + notification.deleteIntent); @@ -545,57 +608,6 @@ public final class NotificationRecord { } pw.println(prefix + "}"); } - pw.println(prefix + "stats=" + stats.toString()); - pw.println(prefix + "mContactAffinity=" + mContactAffinity); - pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive); - pw.println(prefix + "mPackagePriority=" + mPackagePriority); - pw.println(prefix + "mPackageVisibility=" + mPackageVisibility); - pw.println(prefix + "mSystemImportance=" - + NotificationListenerService.Ranking.importanceToString(mSystemImportance)); - pw.println(prefix + "mAsstImportance=" - + NotificationListenerService.Ranking.importanceToString(mAssistantImportance)); - pw.println(prefix + "mImportance=" - + NotificationListenerService.Ranking.importanceToString(mImportance)); - pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation()); - pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked); - pw.println(prefix + "mIntercept=" + mIntercept); - pw.println(prefix + "mHidden==" + mHidden); - pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey); - pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs); - pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs); - pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs); - pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs); - pw.println(prefix + "mInterruptionTimeMs=" + mInterruptionTimeMs); - pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects); - if (mPreChannelsNotification) { - pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x", - notification.defaults, notification.flags)); - pw.println(prefix + "n.sound=" + notification.sound); - pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType); - pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes); - pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", - notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); - pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate)); - } - pw.println(prefix + "mSound= " + mSound); - pw.println(prefix + "mVibration= " + mVibration); - pw.println(prefix + "mAttributes= " + mAttributes); - pw.println(prefix + "mLight= " + mLight); - pw.println(prefix + "mShowBadge=" + mShowBadge); - pw.println(prefix + "mColorized=" + notification.isColorized()); - pw.println(prefix + "mAllowBubble=" + mAllowBubble); - pw.println(prefix + "isBubble=" + notification.isBubbleNotification()); - pw.println(prefix + "mIsInterruptive=" + mIsInterruptive); - pw.println(prefix + "effectiveNotificationChannel=" + getChannel()); - if (getPeopleOverride() != null) { - pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride())); - } - if (getSnoozeCriteria() != null) { - pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria())); - } - pw.println(prefix + "mAdjustments=" + mAdjustments); - pw.println(prefix + "shortcut=" + notification.getShortcutId() - + " found valid? " + (mShortcutInfo != null)); } @Override diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 5417275bc8f1..13cd6e547629 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -417,7 +417,9 @@ public class ZenModeHelper { if (mConfig == null) return; newConfig = mConfig.copy(); - setAutomaticZenRuleStateLocked(newConfig, newConfig.automaticRules.get(id), condition); + ArrayList<ZenRule> rules = new ArrayList<>(); + rules.add(newConfig.automaticRules.get(id)); + setAutomaticZenRuleStateLocked(newConfig, rules, condition); } } @@ -428,31 +430,34 @@ public class ZenModeHelper { newConfig = mConfig.copy(); setAutomaticZenRuleStateLocked(newConfig, - findMatchingRule(newConfig, ruleDefinition, condition), + findMatchingRules(newConfig, ruleDefinition, condition), condition); } } - private void setAutomaticZenRuleStateLocked(ZenModeConfig config, ZenRule rule, + private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, Condition condition) { - if (rule == null) return; + if (rules == null || rules.isEmpty()) return; - rule.condition = condition; - updateSnoozing(rule); - setConfigLocked(config, rule.component, "conditionChanged"); + for (ZenRule rule : rules) { + rule.condition = condition; + updateSnoozing(rule); + setConfigLocked(config, rule.component, "conditionChanged"); + } } - private ZenRule findMatchingRule(ZenModeConfig config, Uri id, Condition condition) { + private List<ZenRule> findMatchingRules(ZenModeConfig config, Uri id, Condition condition) { + List<ZenRule> matchingRules= new ArrayList<>(); if (ruleMatches(id, condition, config.manualRule)) { - return config.manualRule; + matchingRules.add(config.manualRule); } else { for (ZenRule automaticRule : config.automaticRules.values()) { if (ruleMatches(id, condition, automaticRule)) { - return automaticRule; + matchingRules.add(automaticRule); } } } - return null; + return matchingRules; } private boolean ruleMatches(Uri id, Condition condition, ZenRule rule) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index fcf5d9691d99..69e8e65166ac 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1848,7 +1848,7 @@ public class PackageManagerService extends IPackageManager.Stub Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mLock) { removeMessages(WRITE_PACKAGE_LIST); - mPermissionManager.writePermissionsStateToPackageSettingsTEMP(); + mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.writePackageListLPr(msg.arg1); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); @@ -3519,7 +3519,7 @@ public class PackageManagerService extends IPackageManager.Stub + ((SystemClock.uptimeMillis()-startTime)/1000f) + " seconds"); - mPermissionManager.readPermissionsStateFromPackageSettingsTEMP(); + mPermissionManager.readStateFromPackageSettingsTEMP(); // If the platform SDK has changed since the last time we booted, // we need to re-grant app permission to catch any new ones that // appear. This is really a hack, and means that apps can in some @@ -21826,7 +21826,7 @@ public class PackageManagerService extends IPackageManager.Stub protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; - mPermissionManager.writePermissionsStateToPackageSettingsTEMP(); + mPermissionManager.writeStateToPackageSettingsTEMP(); DumpState dumpState = new DumpState(); boolean fullPreferred = false; @@ -23706,7 +23706,7 @@ public class PackageManagerService extends IPackageManager.Stub mDirtyUsers.remove(userId); mUserNeedsBadging.delete(userId); mPermissionManager.onUserRemoved(userId); - mPermissionManager.writePermissionsStateToPackageSettingsTEMP(); + mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.removeUserLPw(userId); mPendingBroadcasts.remove(userId); mInstantAppRegistry.onUserRemovedLPw(userId); @@ -23807,9 +23807,9 @@ public class PackageManagerService extends IPackageManager.Stub boolean readPermissionStateForUser(@UserIdInt int userId) { synchronized (mPackages) { - mPermissionManager.writePermissionsStateToPackageSettingsTEMP(); + mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.readPermissionStateForUserSyncLPr(userId); - mPermissionManager.readPermissionsStateFromPackageSettingsTEMP(); + mPermissionManager.readStateFromPackageSettingsTEMP(); return mPmInternal.isPermissionUpgradeNeeded(userId); } } @@ -25823,12 +25823,12 @@ public class PackageManagerService extends IPackageManager.Stub /** * Temporary method that wraps mSettings.writeLPr() and calls - * mPermissionManager.writePermissionsStateToPackageSettingsTEMP() beforehand. + * mPermissionManager.writeStateToPackageSettingsTEMP() beforehand. * * TODO(zhanghai): This should be removed once we finish migration of permission storage. */ private void writeSettingsLPrTEMP() { - mPermissionManager.writePermissionsStateToPackageSettingsTEMP(); + mPermissionManager.writeStateToPackageSettingsTEMP(); mSettings.writeLPr(); } } diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 962638b4f63c..865b8a1e97eb 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -36,6 +36,7 @@ import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.server.pm.DumpState; import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageSettingBase; @@ -139,6 +140,10 @@ public final class BasePermission { this.perm = perm; } + public boolean hasGids() { + return !ArrayUtils.isEmpty(gids); + } + public int[] computeGids(int userId) { if (perUser) { final int[] userGids = new int[gids.length]; @@ -419,9 +424,9 @@ public final class BasePermission { } public void enforceDeclaredUsedAndRuntimeOrDevelopment(AndroidPackage pkg, - PermissionsState permsState) { + UidPermissionState uidState) { int index = pkg.getRequestedPermissions().indexOf(name); - if (!permsState.hasRequestedPermission(name) && index == -1) { + if (!uidState.hasRequestedPermission(name) && index == -1) { throw new SecurityException("Package " + pkg.getPackageName() + " has not requested permission " + name); } diff --git a/services/core/java/com/android/server/pm/permission/DevicePermissionState.java b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java new file mode 100644 index 000000000000..b9456acfced5 --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 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.server.pm.permission; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** + * Permission state for this device. + */ +public final class DevicePermissionState { + @GuardedBy("mLock") + @NonNull + private final SparseArray<UserPermissionState> mUserStates = new SparseArray<>(); + + @NonNull + private final Object mLock; + + public DevicePermissionState(@NonNull Object lock) { + mLock = lock; + } + + @Nullable + public UserPermissionState getUserState(@UserIdInt int userId) { + synchronized (mLock) { + return mUserStates.get(userId); + } + } + + @NonNull + public UserPermissionState getOrCreateUserState(@UserIdInt int userId) { + synchronized (mLock) { + UserPermissionState userState = mUserStates.get(userId); + if (userState == null) { + userState = new UserPermissionState(mLock); + mUserStates.put(userId, userState); + } + return userState; + } + } + + public void removeUserState(@UserIdInt int userId) { + synchronized (mLock) { + mUserStates.delete(userId); + } + } + + public int[] getUserIds() { + synchronized (mLock) { + final int userStatesSize = mUserStates.size(); + final int[] userIds = new int[userStatesSize]; + for (int i = 0; i < userStatesSize; i++) { + final int userId = mUserStates.keyAt(i); + userIds[i] = userId; + } + return userIds; + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 1cfc5b135cfa..75e5944b3cb4 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -148,12 +148,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal.Default import com.android.server.pm.permission.PermissionManagerServiceInternal.DefaultDialerProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal.DefaultHomeProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal.PermissionCallback; -import com.android.server.pm.permission.PermissionsState.PermissionState; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; -import libcore.util.EmptyArray; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -226,8 +223,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { /** Internal connection to the user manager */ private final UserManagerInternal mUserManagerInt; - /** Maps from App ID to PermissionsState */ - private final SparseArray<PermissionsState> mAppIdStates = new SparseArray<>(); + @NonNull + private final DevicePermissionState mState; /** Permission controller: User space permission management */ private PermissionControllerManager mPermissionControllerManager; @@ -395,6 +392,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); mUserManagerInt = LocalServices.getService(UserManagerInternal.class); mSettings = new PermissionSettings(mLock); + mState = new DevicePermissionState(mLock); mAppOpsManager = context.getSystemService(AppOpsManager.class); mHandlerThread = new ServiceThread(TAG, @@ -681,12 +679,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) { return 0; } - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); return 0; } - return permissionsState.getPermissionFlags(permName, userId); + return uidState.getPermissionFlags(permName); } @Override @@ -788,14 +786,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { throw new IllegalArgumentException("Unknown permission: " + permName); } - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); return; } - final boolean hadState = - permissionsState.getRuntimePermissionState(permName, userId) != null; + final boolean hadState = uidState.getPermissionState(permName) != null; if (!hadState) { boolean isRequested = false; // Fast path, the current package has requested the permission. @@ -822,20 +819,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } final boolean permissionUpdated = - permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues); + uidState.updatePermissionFlags(bp, flagMask, flagValues); if (permissionUpdated && bp.isRuntime()) { notifyRuntimePermissionStateChanged(packageName, userId); } if (permissionUpdated && callback != null) { // Install and runtime permissions are stored in different places, // so figure out what permission changed and persist the change. - if (permissionsState.getInstallPermissionState(permName) != null) { + if (!bp.isRuntime()) { int userUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid())); callback.onInstallPermissionUpdatedNotifyListener(userUid); - } else if (permissionsState.getRuntimePermissionState(permName, userId) != null - || hadState) { - callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, - pkg.getUid()); + } else { + callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid()); } } } @@ -868,13 +863,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean[] changed = new boolean[1]; mPackageManagerInt.forEachPackage(pkg -> { - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); return; } - changed[0] |= permissionsState.updatePermissionFlagsForAllPermissions( - userId, effectiveFlagMask, effectiveFlagValues); + changed[0] |= uidState.updatePermissionFlagsForAllPermissions( + effectiveFlagMask, effectiveFlagValues); mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid()); }); @@ -926,19 +922,20 @@ public class PermissionManagerService extends IPermissionManager.Stub { } final int uid = UserHandle.getUid(userId, pkg.getUid()); - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); return PackageManager.PERMISSION_DENIED; } - if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) { + if (checkSinglePermissionInternal(uid, uidState, permissionName)) { return PackageManager.PERMISSION_GRANTED; } final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName); if (fullerPermissionName != null - && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) { + && checkSinglePermissionInternal(uid, uidState, fullerPermissionName)) { return PackageManager.PERMISSION_GRANTED; } @@ -946,8 +943,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private boolean checkSinglePermissionInternal(int uid, - @NonNull PermissionsState permissionsState, @NonNull String permissionName) { - if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) { + @NonNull UidPermissionState uidState, @NonNull String permissionName) { + if (!uidState.hasPermission(permissionName)) { return false; } @@ -1141,9 +1138,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { final long identity = Binder.clearCallingIdentity(); try { - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); return null; } @@ -1164,7 +1161,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { for (int i = 0; i < permissionCount; i++) { final String permissionName = pkg.getRequestedPermissions().get(i); final int currentFlags = - permissionsState.getPermissionFlags(permissionName, userId); + uidState.getPermissionFlags(permissionName); if ((currentFlags & queryFlags) != 0) { if (whitelistedPermissions == null) { whitelistedPermissions = new ArrayList<>(); @@ -1453,13 +1450,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { throw new IllegalArgumentException("Unknown package: " + packageName); } - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); return; } - bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState); + bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, uidState); // If a permission review is required for legacy apps we represent // their permissions as always granted runtime ones since we need @@ -1472,7 +1470,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid())); - final int flags = permissionsState.getPermissionFlags(permName, userId); + final int flags = uidState.getPermissionFlags(permName); if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) { Log.e(TAG, "Cannot grant system fixed permission " + permName + " for package " + packageName); @@ -1502,8 +1500,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (bp.isDevelopment()) { // Development permissions must be handled specially, since they are not // normal runtime permissions. For now they apply to all users. - if (permissionsState.grantInstallPermission(bp) - != PERMISSION_OPERATION_FAILURE) { + // TODO(zhanghai): We are breaking the behavior above by making all permission state + // per-user. It isn't documented behavior and relatively rarely used anyway. + if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) { if (callback != null) { callback.onInstallPermissionGranted(); } @@ -1521,7 +1520,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { return; } - final int result = permissionsState.grantRuntimePermission(bp, userId); + final int result = uidState.grantPermission(bp); switch (result) { case PERMISSION_OPERATION_FAILURE: { return; @@ -1617,13 +1616,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { throw new IllegalArgumentException("Unknown permission: " + permName); } - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); return; } - bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, permissionsState); + bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, uidState); // If a permission review is required for legacy apps we represent // their permissions as always granted runtime ones since we need @@ -1634,7 +1634,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { return; } - final int flags = permissionsState.getPermissionFlags(permName, userId); + final int flags = uidState.getPermissionFlags(permName); // Only the system may revoke SYSTEM_FIXED permissions. if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0 && UserHandle.getCallingAppId() != Process.SYSTEM_UID) { @@ -1649,8 +1649,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (bp.isDevelopment()) { // Development permissions must be handled specially, since they are not // normal runtime permissions. For now they apply to all users. - if (permissionsState.revokeInstallPermission(bp) - != PERMISSION_OPERATION_FAILURE) { + // TODO(zhanghai): We are breaking the behavior above by making all permission state + // per-user. It isn't documented behavior and relatively rarely used anyway. + if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) { if (callback != null) { mDefaultPermissionCallback.onInstallPermissionRevoked(); } @@ -1659,12 +1660,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } // Permission is already revoked, no need to do anything. - if (!permissionsState.hasRuntimePermission(permName, userId)) { + if (!uidState.hasPermission(permName)) { return; } - if (permissionsState.revokeRuntimePermission(bp, userId) - == PERMISSION_OPERATION_FAILURE) { + if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) { return; } @@ -2466,19 +2466,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private void onUserRemoved(@UserIdInt int userId) { synchronized (mLock) { - final int appIdStatesSize = mAppIdStates.size(); - for (int i = 0; i < appIdStatesSize; i++) { - PermissionsState permissionsState = mAppIdStates.valueAt(i); - for (PermissionState permissionState - : permissionsState.getRuntimePermissionStates(userId)) { - BasePermission bp = mSettings.getPermission(permissionState.getName()); - if (bp != null) { - permissionsState.revokeRuntimePermission(bp, userId); - permissionsState.updatePermissionFlags(bp, userId, - PackageManager.MASK_PERMISSION_FLAGS_ALL, 0); - } - } - } + mState.removeUserState(userId); } } @@ -2489,19 +2477,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (ps == null) { return Collections.emptySet(); } - final PermissionsState permissionsState = getPermissionsState(ps); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName); + final UidPermissionState uidState = getUidState(ps, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); return Collections.emptySet(); } if (!ps.getInstantApp(userId)) { - return permissionsState.getPermissions(userId); + return uidState.getPermissions(); } else { // Install permission state is shared among all users, but instant app state is // per-user, so we can only filter it here unless we make install permission state // per-user as well. - final Set<String> instantPermissions = new ArraySet<>(permissionsState.getPermissions( - userId)); + final Set<String> instantPermissions = new ArraySet<>(uidState.getPermissions()); instantPermissions.removeIf(permissionName -> { BasePermission permission = mSettings.getPermission(permissionName); if (permission == null) { @@ -2533,12 +2520,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (ps == null) { return null; } - final PermissionsState permissionsState = getPermissionsState(ps); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + packageName); + final UidPermissionState uidState = getUidState(ps, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); return null; } - return permissionsState.computeGids(userId); + return uidState.computeGids(userId); } /** @@ -2575,15 +2562,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (ps == null) { return; } - final PermissionsState permissionsState = getOrCreatePermissionsState(ps); final int[] userIds = getAllUserIds(); boolean runtimePermissionsRevoked = false; int[] updatedUserIds = EMPTY_INT_ARRAY; - for (int userId : userIds) { - if (permissionsState.isMissing(userId)) { + for (final int userId : userIds) { + final UserPermissionState userState = mState.getOrCreateUserState(userId); + final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId()); + + if (uidState.isMissing()) { Collection<String> requestedPermissions; int targetSdkVersion; if (!ps.isSharedUser()) { @@ -2611,222 +2600,220 @@ public class PermissionManagerService extends IPermissionManager.Stub { && permission.isRuntime() && !permission.isRemoved()) { if (permission.isHardOrSoftRestricted() || permission.isImmutablyRestricted()) { - permissionsState.updatePermissionFlags(permission, userId, + uidState.updatePermissionFlags(permission, FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT); } if (targetSdkVersion < Build.VERSION_CODES.M) { - permissionsState.updatePermissionFlags(permission, userId, + uidState.updatePermissionFlags(permission, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT); - permissionsState.grantRuntimePermission(permission, userId); + uidState.grantPermission(permission); } } } - permissionsState.setMissing(false, userId); + uidState.setMissing(false); updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); } - } - PermissionsState origPermissions = permissionsState; + UidPermissionState origState = uidState; - boolean changedInstallPermission = false; + boolean changedInstallPermission = false; - if (replace) { - ps.setInstallPermissionsFixed(false); - if (!ps.isSharedUser()) { - origPermissions = new PermissionsState(permissionsState); - permissionsState.reset(); - } else { - // We need to know only about runtime permission changes since the - // calling code always writes the install permissions state but - // the runtime ones are written only if changed. The only cases of - // changed runtime permissions here are promotion of an install to - // runtime and revocation of a runtime from a shared user. - synchronized (mLock) { - updatedUserIds = revokeUnusedSharedUserPermissionsLocked( - ps.getSharedUser().getPackages(), permissionsState, userIds); - if (!ArrayUtils.isEmpty(updatedUserIds)) { - runtimePermissionsRevoked = true; + if (replace) { + userState.setInstallPermissionsFixed(ps.name, false); + if (!ps.isSharedUser()) { + origState = new UidPermissionState(uidState); + uidState.reset(); + } else { + // We need to know only about runtime permission changes since the + // calling code always writes the install permissions state but + // the runtime ones are written only if changed. The only cases of + // changed runtime permissions here are promotion of an install to + // runtime and revocation of a runtime from a shared user. + synchronized (mLock) { + if (revokeUnusedSharedUserPermissionsLocked( + ps.getSharedUser().getPackages(), uidState)) { + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + runtimePermissionsRevoked = true; + } } } } - } - permissionsState.setGlobalGids(mGlobalGids); + uidState.setGlobalGids(mGlobalGids); - ArraySet<String> newImplicitPermissions = new ArraySet<>(); + ArraySet<String> newImplicitPermissions = new ArraySet<>(); - final int N = pkg.getRequestedPermissions().size(); - for (int i = 0; i < N; i++) { - final String permName = pkg.getRequestedPermissions().get(i); - final BasePermission bp = mSettings.getPermission(permName); - final boolean appSupportsRuntimePermissions = - pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; - String upgradedActivityRecognitionPermission = null; + final int N = pkg.getRequestedPermissions().size(); + for (int i = 0; i < N; i++) { + final String permName = pkg.getRequestedPermissions().get(i); + final BasePermission bp = mSettings.getPermission(permName); + final boolean appSupportsRuntimePermissions = + pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; + String upgradedActivityRecognitionPermission = null; - if (DEBUG_INSTALL && bp != null) { - Log.i(TAG, "Package " + pkg.getPackageName() - + " checking " + permName + ": " + bp); - } + if (DEBUG_INSTALL && bp != null) { + Log.i(TAG, "Package " + pkg.getPackageName() + + " checking " + permName + ": " + bp); + } - if (bp == null || getSourcePackageSetting(bp) == null) { - if (packageOfInterest == null || packageOfInterest.equals( - pkg.getPackageName())) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Unknown permission " + permName - + " in package " + pkg.getPackageName()); + if (bp == null || getSourcePackageSetting(bp) == null) { + if (packageOfInterest == null || packageOfInterest.equals( + pkg.getPackageName())) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Unknown permission " + permName + + " in package " + pkg.getPackageName()); + } } + continue; } - continue; - } - // Cache newImplicitPermissions before modifing permissionsState as for the shared - // uids the original and new state are the same object - if (!origPermissions.hasRequestedPermission(permName) - && (pkg.getImplicitPermissions().contains(permName) - || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { - if (pkg.getImplicitPermissions().contains(permName)) { - // If permName is an implicit permission, try to auto-grant - newImplicitPermissions.add(permName); + // Cache newImplicitPermissions before modifing permissionsState as for the shared + // uids the original and new state are the same object + if (!origState.hasRequestedPermission(permName) + && (pkg.getImplicitPermissions().contains(permName) + || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) { + if (pkg.getImplicitPermissions().contains(permName)) { + // If permName is an implicit permission, try to auto-grant + newImplicitPermissions.add(permName); - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, permName + " is newly added for " + pkg.getPackageName()); - } - } else { - // Special case for Activity Recognition permission. Even if AR permission - // is not an implicit permission we want to add it to the list (try to - // auto-grant it) if the app was installed on a device before AR permission - // was split, regardless of if the app now requests the new AR permission - // or has updated its target SDK and AR is no longer implicit to it. - // This is a compatibility workaround for apps when AR permission was - // split in Q. - final List<SplitPermissionInfoParcelable> permissionList = - getSplitPermissions(); - int numSplitPerms = permissionList.size(); - for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { - SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum); - String splitPermName = sp.getSplitPermission(); - if (sp.getNewPermissions().contains(permName) - && origPermissions.hasInstallPermission(splitPermName)) { - upgradedActivityRecognitionPermission = splitPermName; - newImplicitPermissions.add(permName); + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, permName + " is newly added for " + pkg.getPackageName()); + } + } else { + // Special case for Activity Recognition permission. Even if AR permission + // is not an implicit permission we want to add it to the list (try to + // auto-grant it) if the app was installed on a device before AR permission + // was split, regardless of if the app now requests the new AR permission + // or has updated its target SDK and AR is no longer implicit to it. + // This is a compatibility workaround for apps when AR permission was + // split in Q. + final List<SplitPermissionInfoParcelable> permissionList = + getSplitPermissions(); + int numSplitPerms = permissionList.size(); + for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { + SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum); + String splitPermName = sp.getSplitPermission(); + if (sp.getNewPermissions().contains(permName) + && origState.hasInstallPermission(splitPermName)) { + upgradedActivityRecognitionPermission = splitPermName; + newImplicitPermissions.add(permName); - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, permName + " is newly added for " - + pkg.getPackageName()); + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, permName + " is newly added for " + + pkg.getPackageName()); + } + break; } - break; } } } - } - - // TODO(b/140256621): The package instant app method has been removed - // as part of work in b/135203078, so this has been commented out in the meantime - // Limit ephemeral apps to ephemeral allowed permissions. -// if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) { -// if (DEBUG_PERMISSIONS) { -// Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() -// + " for package " + pkg.getPackageName()); -// } -// continue; -// } - if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { - if (DEBUG_PERMISSIONS) { - Log.i(TAG, "Denying runtime-only permission " + bp.getName() - + " for package " + pkg.getPackageName()); + // TODO(b/140256621): The package instant app method has been removed + // as part of work in b/135203078, so this has been commented out in the meantime + // Limit ephemeral apps to ephemeral allowed permissions. + // if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) { + // if (DEBUG_PERMISSIONS) { + // Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + // + " for package " + pkg.getPackageName()); + // } + // continue; + // } + + if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) { + if (DEBUG_PERMISSIONS) { + Log.i(TAG, "Denying runtime-only permission " + bp.getName() + + " for package " + pkg.getPackageName()); + } + continue; } - continue; - } - - final String perm = bp.getName(); - boolean allowedSig = false; - int grant = GRANT_DENIED; - // Keep track of app op permissions. - if (bp.isAppOp()) { - mSettings.addAppOpPackage(perm, pkg.getPackageName()); - } + final String perm = bp.getName(); + boolean allowedSig = false; + int grant = GRANT_DENIED; - if (bp.isNormal()) { - // For all apps normal permissions are install time ones. - grant = GRANT_INSTALL; - } else if (bp.isRuntime()) { - if (origPermissions.hasInstallPermission(bp.getName()) - || upgradedActivityRecognitionPermission != null) { - // Before Q we represented some runtime permissions as install permissions, - // in Q we cannot do this anymore. Hence upgrade them all. - grant = GRANT_UPGRADE; - } else { - // For modern apps keep runtime permissions unchanged. - grant = GRANT_RUNTIME; + // Keep track of app op permissions. + if (bp.isAppOp()) { + mSettings.addAppOpPackage(perm, pkg.getPackageName()); } - } else if (bp.isSignature()) { - // For all apps signature permissions are install time ones. - allowedSig = shouldGrantSignaturePermission(perm, pkg, ps, bp, origPermissions); - if (allowedSig) { + + if (bp.isNormal()) { + // For all apps normal permissions are install time ones. grant = GRANT_INSTALL; + } else if (bp.isRuntime()) { + if (origState.hasInstallPermission(bp.getName()) + || upgradedActivityRecognitionPermission != null) { + // Before Q we represented some runtime permissions as install permissions, + // in Q we cannot do this anymore. Hence upgrade them all. + grant = GRANT_UPGRADE; + } else { + // For modern apps keep runtime permissions unchanged. + grant = GRANT_RUNTIME; + } + } else if (bp.isSignature()) { + // For all apps signature permissions are install time ones. + allowedSig = shouldGrantSignaturePermission(perm, pkg, ps, bp, origState); + if (allowedSig) { + grant = GRANT_INSTALL; + } } - } - if (grant != GRANT_DENIED) { - if (!ps.isSystem() && ps.areInstallPermissionsFixed() && !bp.isRuntime()) { - // If this is an existing, non-system package, then - // we can't add any new permissions to it. Runtime - // permissions can be added any time - they ad dynamic. - if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { - // Except... if this is a permission that was added - // to the platform (note: need to only do this when - // updating the platform). - if (!isNewPlatformPermissionForPackage(perm, pkg)) { - grant = GRANT_DENIED; + if (grant != GRANT_DENIED) { + if (!ps.isSystem() && userState.areInstallPermissionsFixed(ps.name) + && !bp.isRuntime()) { + // If this is an existing, non-system package, then + // we can't add any new permissions to it. Runtime + // permissions can be added any time - they ad dynamic. + if (!allowedSig && !origState.hasInstallPermission(perm)) { + // Except... if this is a permission that was added + // to the platform (note: need to only do this when + // updating the platform). + if (!isNewPlatformPermissionForPackage(perm, pkg)) { + grant = GRANT_DENIED; + } } } } - } - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Considering granting permission " + perm + " to package " - + pkg.getPackageName()); - } + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Considering granting permission " + perm + " to package " + + pkg.getPackageName()); + } - synchronized (mLock) { - if (grant != GRANT_DENIED) { - switch (grant) { - case GRANT_INSTALL: { - // Revoke this as runtime permission to handle the case of - // a runtime permission being downgraded to an install one. - // Also in permission review mode we keep dangerous permissions - // for legacy apps - for (int userId : userIds) { - if (origPermissions.getRuntimePermissionState( - perm, userId) != null) { + synchronized (mLock) { + if (grant != GRANT_DENIED) { + switch (grant) { + case GRANT_INSTALL: { + // Revoke this as runtime permission to handle the case of + // a runtime permission being downgraded to an install one. + // Also in permission review mode we keep dangerous permissions + // for legacy apps + final PermissionState origPermissionState = + origState.getPermissionState(perm); + if (origPermissionState != null + && origPermissionState.isRuntime()) { // Revoke the runtime permission and clear the flags. - origPermissions.revokeRuntimePermission(bp, userId); - origPermissions.updatePermissionFlags(bp, userId, + origState.revokePermission(bp); + origState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0); // If we revoked a permission permission, we have to write. updatedUserIds = ArrayUtils.appendInt( updatedUserIds, userId); } - } - // Grant an install permission. - if (permissionsState.grantInstallPermission(bp) != - PERMISSION_OPERATION_FAILURE) { - changedInstallPermission = true; - } - } break; + // Grant an install permission. + if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) { + changedInstallPermission = true; + } + } break; - case GRANT_RUNTIME: { - boolean hardRestricted = bp.isHardRestricted(); - boolean softRestricted = bp.isSoftRestricted(); + case GRANT_RUNTIME: { + boolean hardRestricted = bp.isHardRestricted(); + boolean softRestricted = bp.isSoftRestricted(); - for (int userId : userIds) { // If permission policy is not ready we don't deal with restricted // permissions as the policy may whitelist some permissions. Once // the policy is initialized we would re-evaluate permissions. @@ -2834,25 +2821,24 @@ public class PermissionManagerService extends IPermissionManager.Stub { mPermissionPolicyInternal != null && mPermissionPolicyInternal.isInitialized(userId); - PermissionState permState = origPermissions - .getRuntimePermissionState(perm, userId); - int flags = permState != null ? permState.getFlags() : 0; + PermissionState origPermState = origState.getPermissionState(perm); + int flags = origPermState != null ? origPermState.getFlags() : 0; boolean wasChanged = false; boolean restrictionExempt = - (origPermissions.getPermissionFlags(bp.name, userId) + (origState.getPermissionFlags(bp.name) & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - boolean restrictionApplied = (origPermissions.getPermissionFlags( - bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; + boolean restrictionApplied = (origState.getPermissionFlags( + bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; if (appSupportsRuntimePermissions) { // If hard restricted we don't allow holding it if (permissionPolicyInitialized && hardRestricted) { if (!restrictionExempt) { - if (permState != null && permState.isGranted() - && permissionsState.revokeRuntimePermission( - bp, userId) != PERMISSION_OPERATION_FAILURE) { + if (origPermState != null && origPermState.isGranted() + && uidState.revokePermission( + bp) != PERMISSION_OPERATION_FAILURE) { wasChanged = true; } if (!restrictionApplied) { @@ -2882,15 +2868,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Hard restricted permissions cannot be held. } else if (!permissionPolicyInitialized || (!hardRestricted || restrictionExempt)) { - if (permState != null && permState.isGranted()) { - if (permissionsState.grantRuntimePermission(bp, userId) + if (origPermState != null && origPermState.isGranted()) { + if (uidState.grantPermission(bp) == PERMISSION_OPERATION_FAILURE) { wasChanged = true; } } } } else { - if (permState == null) { + if (origPermState == null) { // New permission if (PLATFORM_PACKAGE_NAME.equals( bp.getSourcePackageName())) { @@ -2902,8 +2888,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - if (!permissionsState.hasRuntimePermission(bp.name, userId) - && permissionsState.grantRuntimePermission(bp, userId) + if (!uidState.hasPermission(bp.name) + && uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) { wasChanged = true; } @@ -2936,36 +2922,32 @@ public class PermissionManagerService extends IPermissionManager.Stub { updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); } - permissionsState.updatePermissionFlags(bp, userId, - MASK_PERMISSION_FLAGS_ALL, flags); - } - } break; - - case GRANT_UPGRADE: { - // Upgrade from Pre-Q to Q permission model. Make all permissions - // runtime - PermissionState permState = origPermissions - .getInstallPermissionState(perm); - int flags = (permState != null) ? permState.getFlags() : 0; - - BasePermission bpToRevoke = - upgradedActivityRecognitionPermission == null - ? bp : mSettings.getPermissionLocked( - upgradedActivityRecognitionPermission); - // Remove install permission - if (origPermissions.revokeInstallPermission(bpToRevoke) - != PERMISSION_OPERATION_FAILURE) { - origPermissions.updatePermissionFlags(bpToRevoke, - UserHandle.USER_ALL, - (MASK_PERMISSION_FLAGS_ALL - & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0); - changedInstallPermission = true; - } + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, + flags); + } break; + + case GRANT_UPGRADE: { + // Upgrade from Pre-Q to Q permission model. Make all permissions + // runtime + PermissionState origPermState = origState.getPermissionState(perm); + int flags = (origPermState != null) ? origPermState.getFlags() : 0; + + BasePermission bpToRevoke = + upgradedActivityRecognitionPermission == null + ? bp : mSettings.getPermissionLocked( + upgradedActivityRecognitionPermission); + // Remove install permission + if (origState.revokePermission(bpToRevoke) + != PERMISSION_OPERATION_FAILURE) { + origState.updatePermissionFlags(bpToRevoke, + (MASK_PERMISSION_FLAGS_ALL + & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0); + changedInstallPermission = true; + } - boolean hardRestricted = bp.isHardRestricted(); - boolean softRestricted = bp.isSoftRestricted(); + boolean hardRestricted = bp.isHardRestricted(); + boolean softRestricted = bp.isSoftRestricted(); - for (int userId : userIds) { // If permission policy is not ready we don't deal with restricted // permissions as the policy may whitelist some permissions. Once // the policy is initialized we would re-evaluate permissions. @@ -2976,18 +2958,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { boolean wasChanged = false; boolean restrictionExempt = - (origPermissions.getPermissionFlags(bp.name, userId) + (origState.getPermissionFlags(bp.name) & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - boolean restrictionApplied = (origPermissions.getPermissionFlags( - bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; + boolean restrictionApplied = (origState.getPermissionFlags( + bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; if (appSupportsRuntimePermissions) { // If hard restricted we don't allow holding it if (permissionPolicyInitialized && hardRestricted) { if (!restrictionExempt) { - if (permState != null && permState.isGranted() - && permissionsState.revokeRuntimePermission( - bp, userId) != PERMISSION_OPERATION_FAILURE) { + if (origPermState != null && origPermState.isGranted() + && uidState.revokePermission( + bp) != PERMISSION_OPERATION_FAILURE) { wasChanged = true; } if (!restrictionApplied) { @@ -3017,15 +2999,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Hard restricted permissions cannot be held. } else if (!permissionPolicyInitialized || (!hardRestricted || restrictionExempt)) { - if (permissionsState.grantRuntimePermission(bp, userId) != - PERMISSION_OPERATION_FAILURE) { + if (uidState.grantPermission(bp) + != PERMISSION_OPERATION_FAILURE) { wasChanged = true; } } } else { - if (!permissionsState.hasRuntimePermission(bp.name, userId) - && permissionsState.grantRuntimePermission(bp, - userId) != PERMISSION_OPERATION_FAILURE) { + if (!uidState.hasPermission(bp.name) + && uidState.grantPermission(bp) + != PERMISSION_OPERATION_FAILURE) { flags |= FLAG_PERMISSION_REVIEW_REQUIRED; wasChanged = true; } @@ -3058,72 +3040,75 @@ public class PermissionManagerService extends IPermissionManager.Stub { updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); } - permissionsState.updatePermissionFlags(bp, userId, + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, flags); - } - } break; + } break; - default: { - if (packageOfInterest == null - || packageOfInterest.equals(pkg.getPackageName())) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + pkg.getPackageName() - + " because it was previously installed without"); + default: { + if (packageOfInterest == null + || packageOfInterest.equals(pkg.getPackageName())) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Not granting permission " + perm + + " to package " + pkg.getPackageName() + + " because it was previously installed without"); + } } - } - } break; - } - } else { - if (permissionsState.revokeInstallPermission(bp) != - PERMISSION_OPERATION_FAILURE) { - // Also drop the permission flags. - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - MASK_PERMISSION_FLAGS_ALL, 0); - changedInstallPermission = true; - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Un-granting permission " + perm - + " from package " + pkg.getPackageName() - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" - + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps)) - + ")"); + } break; } - } else if (bp.isAppOp()) { - // Don't print warning for app op permissions, since it is fine for them - // not to be granted, there is a UI for the user to decide. - if (DEBUG_PERMISSIONS - && (packageOfInterest == null - || packageOfInterest.equals(pkg.getPackageName()))) { - Slog.i(TAG, "Not granting permission " + perm - + " to package " + pkg.getPackageName() - + " (protectionLevel=" + bp.getProtectionLevel() - + " flags=0x" - + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, ps)) - + ")"); + } else { + if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) { + // Also drop the permission flags. + uidState.updatePermissionFlags(bp, + MASK_PERMISSION_FLAGS_ALL, 0); + changedInstallPermission = true; + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Un-granting permission " + perm + + " from package " + pkg.getPackageName() + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, + ps)) + + ")"); + } + } else if (bp.isAppOp()) { + // Don't print warning for app op permissions, since it is fine for them + // not to be granted, there is a UI for the user to decide. + if (DEBUG_PERMISSIONS + && (packageOfInterest == null + || packageOfInterest.equals(pkg.getPackageName()))) { + Slog.i(TAG, "Not granting permission " + perm + + " to package " + pkg.getPackageName() + + " (protectionLevel=" + bp.getProtectionLevel() + + " flags=0x" + + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg, + ps)) + + ")"); + } } } } } - } - if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() && - !ps.isSystem() || ps.getPkgState().isUpdatedSystemApp()) { - // This is the first that we have heard about this package, so the - // permissions we have now selected are fixed until explicitly - // changed. - ps.setInstallPermissionsFixed(true); - } + if ((changedInstallPermission || replace) + && !userState.areInstallPermissionsFixed(ps.name) + && !ps.isSystem() || ps.getPkgState().isUpdatedSystemApp()) { + // This is the first that we have heard about this package, so the + // permissions we have now selected are fixed until explicitly + // changed. + userState.setInstallPermissionsFixed(ps.name, true); + } - synchronized (mLock) { - updatedUserIds = revokePermissionsNoLongerImplicitLocked(permissionsState, pkg, - userIds, updatedUserIds); - updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions, - permissionsState, pkg, newImplicitPermissions, userIds, updatedUserIds); - updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds, - updatedUserIds); + synchronized (mLock) { + updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg, + userId, updatedUserIds); + updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState, + uidState, pkg, newImplicitPermissions, userId, updatedUserIds); + } } + updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds, + updatedUserIds); + // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important // for shared users. // Persist the runtime permissions state for users with changes. If permissions @@ -3159,40 +3144,38 @@ public class PermissionManagerService extends IPermissionManager.Stub { * * @return The updated value of the {@code updatedUserIds} parameter */ - private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull PermissionsState ps, - @NonNull AndroidPackage pkg, @NonNull int[] userIds, @NonNull int[] updatedUserIds) { + private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps, + @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) { String pkgName = pkg.getPackageName(); boolean supportsRuntimePermissions = pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M; - for (int userId : userIds) { - for (String permission : ps.getPermissions(userId)) { - if (!pkg.getImplicitPermissions().contains(permission)) { - if (!ps.hasInstallPermission(permission)) { - int flags = ps.getRuntimePermissionState(permission, userId).getFlags(); + for (String permission : ps.getPermissions()) { + if (!pkg.getImplicitPermissions().contains(permission)) { + if (!ps.hasInstallPermission(permission)) { + int flags = ps.getPermissionFlags(permission); - if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { - BasePermission bp = mSettings.getPermissionLocked(permission); + if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) { + BasePermission bp = mSettings.getPermissionLocked(permission); - int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; + int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED; - if ((flags & BLOCKING_PERMISSION_FLAGS) == 0 - && supportsRuntimePermissions) { - int revokeResult = ps.revokeRuntimePermission(bp, userId); - if (revokeResult != PERMISSION_OPERATION_FAILURE) { - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, "Revoking runtime permission " - + permission + " for " + pkgName - + " as it is now requested"); - } + if ((flags & BLOCKING_PERMISSION_FLAGS) == 0 + && supportsRuntimePermissions) { + int revokeResult = ps.revokePermission(bp); + if (revokeResult != PERMISSION_OPERATION_FAILURE) { + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, "Revoking runtime permission " + + permission + " for " + pkgName + + " as it is now requested"); } - - flagsToRemove |= USER_PERMISSION_FLAGS; } - ps.updatePermissionFlags(bp, userId, flagsToRemove, 0); - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + flagsToRemove |= USER_PERMISSION_FLAGS; } + + ps.updatePermissionFlags(bp, flagsToRemove, 0); + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); } } } @@ -3213,12 +3196,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { * @param newPerm The permission to inherit to * @param ps The permission state of the package * @param pkg The package requesting the permissions - * @param userId The user the permission belongs to */ private void inheritPermissionStateToNewImplicitPermissionLocked( @NonNull ArraySet<String> sourcePerms, @NonNull String newPerm, - @NonNull PermissionsState ps, @NonNull AndroidPackage pkg, - @UserIdInt int userId) { + @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) { String pkgName = pkg.getPackageName(); boolean isGranted = false; int flags = 0; @@ -3226,17 +3207,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { int numSourcePerm = sourcePerms.size(); for (int i = 0; i < numSourcePerm; i++) { String sourcePerm = sourcePerms.valueAt(i); - if ((ps.hasRuntimePermission(sourcePerm, userId)) - || ps.hasInstallPermission(sourcePerm)) { + if (ps.hasPermission(sourcePerm)) { if (!isGranted) { flags = 0; } isGranted = true; - flags |= ps.getPermissionFlags(sourcePerm, userId); + flags |= ps.getPermissionFlags(sourcePerm); } else { if (!isGranted) { - flags |= ps.getPermissionFlags(sourcePerm, userId); + flags |= ps.getPermissionFlags(sourcePerm); } } } @@ -3247,11 +3227,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { + " for " + pkgName); } - ps.grantRuntimePermission(mSettings.getPermissionLocked(newPerm), userId); + ps.grantPermission(mSettings.getPermissionLocked(newPerm)); } // Add permission flags - ps.updatePermissionFlags(mSettings.getPermission(newPerm), userId, flags, flags); + ps.updatePermissionFlags(mSettings.getPermission(newPerm), flags, flags); } /** @@ -3283,15 +3263,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { * @param origPs The permission state of the package before the split * @param ps The new permission state * @param pkg The package the permission belongs to - * @param userIds All user IDs in the system, must be passed in because this method is locked + * @param userId The user ID * @param updatedUserIds List of users for which the permission state has already been changed * * @return List of users for which the permission state has been changed */ private @NonNull int[] setInitialGrantForNewImplicitPermissionsLocked( - @NonNull PermissionsState origPs, @NonNull PermissionsState ps, + @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg, @NonNull ArraySet<String> newImplicitPermissions, - @NonNull int[] userIds, @NonNull int[] updatedUserIds) { + @UserIdInt int userId, @NonNull int[] updatedUserIds) { String pkgName = pkg.getPackageName(); ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>(); @@ -3325,35 +3305,33 @@ public class PermissionManagerService extends IPermissionManager.Stub { if (!ps.hasInstallPermission(newPerm)) { BasePermission bp = mSettings.getPermissionLocked(newPerm); - for (int userId : userIds) { - if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) { - ps.updatePermissionFlags(bp, userId, - FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, - FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); - } - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); + if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) { + ps.updatePermissionFlags(bp, + FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, + FLAG_PERMISSION_REVOKE_WHEN_REQUESTED); + } + updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - boolean inheritsFromInstallPerm = false; - for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size(); - sourcePermNum++) { - if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) { - inheritsFromInstallPerm = true; - break; - } + boolean inheritsFromInstallPerm = false; + for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size(); + sourcePermNum++) { + if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) { + inheritsFromInstallPerm = true; + break; } + } - if (!origPs.hasRequestedPermission(sourcePerms) - && !inheritsFromInstallPerm) { - // Both permissions are new so nothing to inherit. - if (DEBUG_PERMISSIONS) { - Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms - + " for " + pkgName + " as split permission is also new"); - } - } else { - // Inherit from new install or existing runtime permissions - inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, - newPerm, ps, pkg, userId); + if (!origPs.hasRequestedPermission(sourcePerms) + && !inheritsFromInstallPerm) { + // Both permissions are new so nothing to inherit. + if (DEBUG_PERMISSIONS) { + Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms + + " for " + pkgName + " as split permission is also new"); } + } else { + // Inherit from new install or existing runtime permissions + inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, + newPerm, ps, pkg); } } } @@ -3484,7 +3462,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private boolean shouldGrantSignaturePermission(String perm, AndroidPackage pkg, - PackageSetting pkgSetting, BasePermission bp, PermissionsState origPermissions) { + PackageSetting pkgSetting, BasePermission bp, UidPermissionState origPermissions) { boolean oemPermission = bp.isOEM(); boolean vendorPrivilegedPermission = bp.isVendorPrivileged(); boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged(); @@ -3760,12 +3738,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { } // Legacy apps have the permission and get user consent on launch. - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); return false; } - return permissionsState.isPermissionReviewRequired(userId); + return uidState.isPermissionReviewRequired(); } private boolean isPackageRequestingPermission(AndroidPackage pkg, String permission) { @@ -3789,9 +3768,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { private void grantRequestedRuntimePermissionsForUser(AndroidPackage pkg, int userId, String[] grantedPermissions, int callingUid, PermissionCallback callback) { - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); return; } @@ -3816,7 +3796,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { && (supportsRuntimePermissions || !bp.isRuntimeOnly()) && (grantedPermissions == null || ArrayUtils.contains(grantedPermissions, permission))) { - final int flags = permissionsState.getPermissionFlags(permission, userId); + final int flags = uidState.getPermissionFlags(permission); if (supportsRuntimePermissions) { // Installer cannot change immutable permissions. if ((flags & immutableFlags) == 0) { @@ -3838,18 +3818,19 @@ public class PermissionManagerService extends IPermissionManager.Stub { private void setWhitelistedRestrictedPermissionsForUsers(@NonNull AndroidPackage pkg, @UserIdInt int[] userIds, @Nullable List<String> permissions, int callingUid, @PermissionWhitelistFlags int whitelistFlags, PermissionCallback callback) { - final PermissionsState permissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); - return; - } - SparseArray<ArraySet<String>> oldGrantedRestrictedPermissions = new SparseArray<>(); boolean updatePermissions = false; final int permissionCount = pkg.getRequestedPermissions().size(); for (int i = 0; i < userIds.length; i++) { int userId = userIds[i]; + final UidPermissionState uidState = getUidState(pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user " + + userId); + continue; + } + for (int j = 0; j < permissionCount; j++) { final String permissionName = pkg.getRequestedPermissions().get(j); @@ -3859,14 +3840,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { continue; } - if (permissionsState.hasPermission(permissionName, userId)) { + if (uidState.hasPermission(permissionName)) { if (oldGrantedRestrictedPermissions.get(userId) == null) { oldGrantedRestrictedPermissions.put(userId, new ArraySet<>()); } oldGrantedRestrictedPermissions.get(userId).add(permissionName); } - final int oldFlags = permissionsState.getPermissionFlags(permissionName, userId); + final int oldFlags = uidState.getPermissionFlags(permissionName); int newFlags = oldFlags; int mask = 0; @@ -3921,8 +3902,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // as whitelisting trumps policy i.e. policy cannot grant a non // grantable permission. if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) { - final boolean isGranted = permissionsState.hasPermission(permissionName, - userId); + final boolean isGranted = uidState.hasPermission(permissionName); if (!isWhitelisted && isGranted) { mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED; newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED; @@ -3958,12 +3938,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { for (int j = 0; j < oldGrantedCount; j++) { final String permission = oldPermsForUser.valueAt(j); // Sometimes we create a new permission state instance during update. - final PermissionsState newPermissionsState = getPermissionsState(pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()); + final UidPermissionState newUidState = getUidState(pkg, userId); + if (newUidState == null) { + Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + + " and user " + userId); continue; } - if (!newPermissionsState.hasPermission(permission, userId)) { + if (!newUidState.hasPermission(permission)) { callback.onPermissionRevoked(pkg.getUid(), userId, null); break; } @@ -4012,9 +3993,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { continue; } - PermissionsState permissionsState = getPermissionsState(deletedPs.pkg); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()); + UidPermissionState uidState = getUidState(deletedPs.pkg, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName() + + " and user " + userId); continue; } @@ -4036,25 +4018,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - // Try to revoke as an install permission which is for all users. // The package is gone - no need to keep flags for applying policy. - permissionsState.updatePermissionFlags(bp, userId, - PackageManager.MASK_PERMISSION_FLAGS_ALL, 0); - - if (permissionsState.revokeInstallPermission(bp) - == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { - affectedUserId = UserHandle.USER_ALL; - } + uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0); // Try to revoke as a runtime permission which is per user. - if (permissionsState.revokeRuntimePermission(bp, userId) + // TODO(zhanghai): This doesn't make sense. revokePermission() doesn't fail, and why are + // we only killing the uid when gids changed, instead of any permission change? + if (uidState.revokePermission(bp) == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { - if (affectedUserId == UserHandle.USER_NULL) { - affectedUserId = userId; - } else if (affectedUserId != userId) { - // Multiple users affected. - affectedUserId = UserHandle.USER_ALL; - } + affectedUserId = userId; } } @@ -4062,12 +4034,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @GuardedBy("mLock") - private int[] revokeUnusedSharedUserPermissionsLocked( - List<AndroidPackage> pkgList, PermissionsState permissionsState, int[] allUserIds) { + private boolean revokeUnusedSharedUserPermissionsLocked( + List<AndroidPackage> pkgList, UidPermissionState uidState) { // Collect all used permissions in the UID final ArraySet<String> usedPermissions = new ArraySet<>(); if (pkgList == null || pkgList.size() == 0) { - return EmptyArray.INT; + return false; } for (AndroidPackage pkg : pkgList) { if (pkg.getRequestedPermissions().isEmpty()) { @@ -4083,44 +4055,27 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - // Prune install permissions - List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates(); - final int installPermCount = installPermStates.size(); - for (int i = installPermCount - 1; i >= 0; i--) { - PermissionState permissionState = installPermStates.get(i); + boolean runtimePermissionChanged = false; + + // Prune permissions + final List<com.android.server.pm.permission.PermissionState> permissionStates = + uidState.getPermissionStates(); + final int permissionStatesSize = permissionStates.size(); + for (int i = permissionStatesSize - 1; i >= 0; i--) { + PermissionState permissionState = permissionStates.get(i); if (!usedPermissions.contains(permissionState.getName())) { BasePermission bp = mSettings.getPermissionLocked(permissionState.getName()); if (bp != null) { - permissionsState.revokeInstallPermission(bp); - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - MASK_PERMISSION_FLAGS_ALL, 0); - } - } - } - - int[] runtimePermissionChangedUserIds = EmptyArray.INT; - - // Prune runtime permissions - for (int userId : allUserIds) { - List<PermissionState> runtimePermStates = permissionsState - .getRuntimePermissionStates(userId); - final int runtimePermCount = runtimePermStates.size(); - for (int i = runtimePermCount - 1; i >= 0; i--) { - PermissionState permissionState = runtimePermStates.get(i); - if (!usedPermissions.contains(permissionState.getName())) { - BasePermission bp = mSettings.getPermissionLocked(permissionState.getName()); - if (bp != null) { - permissionsState.revokeRuntimePermission(bp, userId); - permissionsState.updatePermissionFlags(bp, userId, - MASK_PERMISSION_FLAGS_ALL, 0); - runtimePermissionChangedUserIds = ArrayUtils.appendInt( - runtimePermissionChangedUserIds, userId); + uidState.revokePermission(bp); + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0); + if (permissionState.isRuntime()) { + runtimePermissionChanged = true; } } } } - return runtimePermissionChangedUserIds; + return runtimePermissionChanged; } /** @@ -4368,15 +4323,19 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } else { mPackageManagerInt.forEachPackage(p -> { - final PermissionsState permissionsState = getPermissionsState(p); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + p.getPackageName()); - return; - } - if (permissionsState.getInstallPermissionState(bp.getName()) != null) { - permissionsState.revokeInstallPermission(bp); - permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, - MASK_PERMISSION_FLAGS_ALL, 0); + final int[] userIds = mUserManagerInt.getUserIds(); + for (final int userId : userIds) { + final UidPermissionState uidState = getUidState(p, userId); + if (uidState == null) { + Slog.e(TAG, "Missing permissions state for " + + p.getPackageName() + " and user " + userId); + return; + } + if (uidState.getPermissionState(bp.getName()) != null) { + uidState.revokePermission(bp); + uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, + 0); + } } }); } @@ -4784,62 +4743,124 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Nullable - private PermissionsState getPermissionsState(@NonNull PackageSetting ps) { - return getPermissionsState(ps.getAppId()); - } - - @Nullable - private PermissionsState getPermissionsState(@NonNull AndroidPackage pkg) { - return getPermissionsState(pkg.getUid()); - } - - @Nullable - private PermissionsState getPermissionsState(int appId) { - synchronized (mLock) { - return mAppIdStates.get(appId); - } + private UidPermissionState getUidState(@NonNull PackageSetting ps, + @UserIdInt int userId) { + return getUidState(ps.getAppId(), userId); } @Nullable - private PermissionsState getOrCreatePermissionsState(@NonNull PackageSetting ps) { - return getOrCreatePermissionsState(ps.getAppId()); + private UidPermissionState getUidState(@NonNull AndroidPackage pkg, + @UserIdInt int userId) { + return getUidState(pkg.getUid(), userId); } @Nullable - private PermissionsState getOrCreatePermissionsState(int appId) { + private UidPermissionState getUidState(int appId, @UserIdInt int userId) { synchronized (mLock) { - PermissionsState state = mAppIdStates.get(appId); - if (state == null) { - state = new PermissionsState(); - mAppIdStates.put(appId, state); + final UserPermissionState userState = mState.getUserState(userId); + if (userState == null) { + return null; } - return state; + return userState.getUidState(appId); } } - private void removePermissionsState(int appId) { + private void removeAppState(int appId) { synchronized (mLock) { - mAppIdStates.remove(appId); + final int[] userIds = mState.getUserIds(); + for (final int userId : userIds) { + final UserPermissionState userState = mState.getUserState(userId); + userState.removeUidState(appId); + } } } - private void readPermissionsStateFromPackageSettings() { + private void readStateFromPackageSettings() { + final int[] userIds = getAllUserIds(); mPackageManagerInt.forEachPackageSetting(ps -> { + final int appId = ps.getAppId(); + final PermissionsState permissionsState = ps.getPermissionsState(); + synchronized (mLock) { - mAppIdStates.put(ps.getAppId(), new PermissionsState(ps.getPermissionsState())); + for (final int userId : userIds) { + final UserPermissionState userState = mState.getOrCreateUserState(userId); + + userState.setInstallPermissionsFixed(ps.name, ps.areInstallPermissionsFixed()); + final UidPermissionState uidState = userState.getOrCreateUidState(appId); + uidState.reset(); + uidState.setGlobalGids(permissionsState.getGlobalGids()); + uidState.setMissing(permissionsState.isMissing(userId)); + readStateFromPermissionStates(uidState, + permissionsState.getInstallPermissionStates(), false); + readStateFromPermissionStates(uidState, + permissionsState.getRuntimePermissionStates(userId), true); + } } }); } - private void writePermissionsStateToPackageSettings() { + private void readStateFromPermissionStates(@NonNull UidPermissionState uidState, + @NonNull List<PermissionsState.PermissionState> permissionStates, boolean isRuntime) { + final int permissionStatesSize = permissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + final PermissionsState.PermissionState permissionState = permissionStates.get(i); + final BasePermission permission = permissionState.getPermission(); + uidState.putPermissionState(permission, isRuntime, permissionState.isGranted(), + permissionState.getFlags()); + } + } + + private void writeStateToPackageSettings() { + final int[] userIds = mState.getUserIds(); mPackageManagerInt.forEachPackageSetting(ps -> { + ps.setInstallPermissionsFixed(false); + final PermissionsState permissionsState = ps.getPermissionsState(); + permissionsState.reset(); + final int appId = ps.getAppId(); + synchronized (mLock) { - final PermissionsState permissionsState = mAppIdStates.get(ps.getAppId()); - if (permissionsState == null) { - Slog.e(TAG, "Missing permissions state for " + ps.name); - return; + for (final int userId : userIds) { + final UserPermissionState userState = mState.getUserState(userId); + if (userState == null) { + Slog.e(TAG, "Missing user state for " + userId); + continue; + } + + if (userState.areInstallPermissionsFixed(ps.name)) { + ps.setInstallPermissionsFixed(true); + } + + final UidPermissionState uidState = userState.getUidState(appId); + if (uidState == null) { + Slog.e(TAG, "Missing permission state for " + ps.name + " and user " + + userId); + continue; + } + + permissionsState.setGlobalGids(uidState.getGlobalGids()); + permissionsState.setMissing(uidState.isMissing(), userId); + final List<PermissionState> permissionStates = uidState.getPermissionStates(); + final int permissionStatesSize = permissionStates.size(); + for (int i = 0; i < permissionStatesSize; i++) { + final PermissionState permissionState = permissionStates.get(i); + + final BasePermission permission = permissionState.getPermission(); + if (permissionState.isGranted()) { + if (permissionState.isRuntime()) { + permissionsState.grantRuntimePermission(permission, userId); + } else { + permissionsState.grantInstallPermission(permission); + } + } + final int flags = permissionState.getFlags(); + if (flags != 0) { + final int flagsUserId = permissionState.isRuntime() ? userId + : UserHandle.USER_ALL; + permissionsState.updatePermissionFlags(permission, flagsUserId, flags, + flags); + } + } } - ps.getPermissionsState().copyFrom(permissionsState); } }); } @@ -4876,12 +4897,12 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.removeAllPermissions(pkg, chatty); } @Override - public void readPermissionsStateFromPackageSettingsTEMP() { - PermissionManagerService.this.readPermissionsStateFromPackageSettings(); + public void readStateFromPackageSettingsTEMP() { + PermissionManagerService.this.readStateFromPackageSettings(); } @Override - public void writePermissionsStateToPackageSettingsTEMP() { - PermissionManagerService.this.writePermissionsStateToPackageSettings(); + public void writeStateToPackageSettingsTEMP() { + PermissionManagerService.this.writeStateToPackageSettings(); } @Override public void onUserRemoved(@UserIdInt int userId) { @@ -4889,7 +4910,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override public void removePermissionsStateTEMP(int appId) { - PermissionManagerService.this.removePermissionsState(appId); + PermissionManagerService.this.removeAppState(appId); } @Override @UserIdInt diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index f319bf495e8b..7f6a1d4284d2 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -266,21 +266,21 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty); /** - * Read {@code PermissionsState} from package settings. + * Read permission state from package settings. * * TODO(zhanghai): This is a temporary method because we should not expose * {@code PackageSetting} which is a implementation detail that permission should not know. * Instead, it should retrieve the legacy state via a defined API. */ - public abstract void readPermissionsStateFromPackageSettingsTEMP(); + public abstract void readStateFromPackageSettingsTEMP(); /** - * Write {@code PermissionsState} from to settings. + * Write permission state to package settings. * * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence * for permission. */ - public abstract void writePermissionsStateToPackageSettingsTEMP(); + public abstract void writeStateToPackageSettingsTEMP(); /** * Notify that a user has been removed and its permission state should be removed as well. diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java new file mode 100644 index 000000000000..2ed9a50353d4 --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/PermissionState.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2020 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.server.pm.permission; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; + +import com.android.internal.annotations.GuardedBy; + +/** + * State for a single permission. + */ +public final class PermissionState { + + @NonNull + private final BasePermission mPermission; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private boolean mRuntime; + + @GuardedBy("mLock") + private boolean mGranted; + + @GuardedBy("mLock") + private int mFlags; + + public PermissionState(@NonNull BasePermission permission, boolean isRuntime) { + mPermission = permission; + mRuntime = isRuntime; + } + + public PermissionState(@NonNull PermissionState other) { + this(other.mPermission, other.mRuntime); + + mGranted = other.mGranted; + mFlags = other.mFlags; + } + + @NonNull + public BasePermission getPermission() { + return mPermission; + } + + @NonNull + public String getName() { + return mPermission.getName(); + } + + @Nullable + public int[] computeGids(@UserIdInt int userId) { + return mPermission.computeGids(userId); + } + + public boolean isRuntime() { + synchronized (mLock) { + return mRuntime; + } + } + + public boolean isGranted() { + synchronized (mLock) { + return mGranted; + } + } + + public boolean grant() { + synchronized (mLock) { + if (mGranted) { + return false; + } + mGranted = true; + UidPermissionState.invalidateCache(); + return true; + } + } + + public boolean revoke() { + synchronized (mLock) { + if (!mGranted) { + return false; + } + mGranted = false; + UidPermissionState.invalidateCache(); + return true; + } + } + + public int getFlags() { + synchronized (mLock) { + return mFlags; + } + } + + public boolean updateFlags(int flagMask, int flagValues) { + synchronized (mLock) { + final int newFlags = flagValues & flagMask; + + // Okay to do before the modification because we hold the lock. + UidPermissionState.invalidateCache(); + + final int oldFlags = mFlags; + mFlags = (mFlags & ~flagMask) | newFlags; + return mFlags != oldFlags; + } + } + + public boolean isDefault() { + synchronized (mLock) { + return !mGranted && mFlags == 0; + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java index bad59cb1b567..4fb2d5fc200e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionsState.java +++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java @@ -86,6 +86,10 @@ public final class PermissionsState { copyFrom(prototype); } + public int[] getGlobalGids() { + return mGlobalGids; + } + /** * Sets the global gids, applicable to all users. * @@ -825,7 +829,7 @@ public final class PermissionsState { PermissionState userState = mUserStates.get(userId); if (userState == null) { - userState = new PermissionState(mPerm.getName()); + userState = new PermissionState(mPerm); mUserStates.put(userId, userState); } @@ -908,7 +912,7 @@ public final class PermissionsState { } return userState.mFlags != oldFlags; } else if (newFlags != 0) { - userState = new PermissionState(mPerm.getName()); + userState = new PermissionState(mPerm); userState.mFlags = newFlags; mUserStates.put(userId, userState); return true; @@ -929,16 +933,16 @@ public final class PermissionsState { } public static final class PermissionState { - private final String mName; + private final BasePermission mPermission; private boolean mGranted; private int mFlags; - public PermissionState(String name) { - mName = name; + public PermissionState(BasePermission permission) { + mPermission = permission; } public PermissionState(PermissionState other) { - mName = other.mName; + mPermission = other.mPermission; mGranted = other.mGranted; mFlags = other.mFlags; } @@ -947,8 +951,12 @@ public final class PermissionsState { return !mGranted && mFlags == 0; } + public BasePermission getPermission() { + return mPermission; + } + public String getName() { - return mName; + return mPermission.getName(); } public boolean isGranted() { diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java new file mode 100644 index 000000000000..4c047ffd30e8 --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2015 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.server.pm.permission; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.pm.PackageManager; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Permission state for a UID. + * <p> + * This class is also responsible for keeping track of the Linux GIDs per + * user for a package or a shared user. The GIDs are computed as a set of + * the GIDs for all granted permissions' GIDs on a per user basis. + */ +public final class UidPermissionState { + /** The permission operation failed. */ + public static final int PERMISSION_OPERATION_FAILURE = -1; + + /** The permission operation succeeded and no gids changed. */ + public static final int PERMISSION_OPERATION_SUCCESS = 0; + + /** The permission operation succeeded and gids changed. */ + public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1; + + private static final int[] NO_GIDS = {}; + + @NonNull + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private ArrayMap<String, PermissionState> mPermissions; + + @NonNull + private int[] mGlobalGids = NO_GIDS; + + private boolean mMissing; + + private boolean mPermissionReviewRequired; + + public UidPermissionState() { + /* do nothing */ + } + + public UidPermissionState(@NonNull UidPermissionState prototype) { + copyFrom(prototype); + } + + /** + * Gets the global gids, applicable to all users. + */ + @NonNull + public int[] getGlobalGids() { + return mGlobalGids; + } + + /** + * Sets the global gids, applicable to all users. + * + * @param globalGids The global gids. + */ + public void setGlobalGids(@NonNull int[] globalGids) { + if (!ArrayUtils.isEmpty(globalGids)) { + mGlobalGids = Arrays.copyOf(globalGids, globalGids.length); + } + } + + static void invalidateCache() { + PackageManager.invalidatePackageInfoCache(); + } + + /** + * Initialized this instance from another one. + * + * @param other The other instance. + */ + public void copyFrom(@NonNull UidPermissionState other) { + if (other == this) { + return; + } + + synchronized (mLock) { + if (mPermissions != null) { + if (other.mPermissions == null) { + mPermissions = null; + } else { + mPermissions.clear(); + } + } + if (other.mPermissions != null) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + final int permissionCount = other.mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String name = other.mPermissions.keyAt(i); + PermissionState permissionState = other.mPermissions.valueAt(i); + mPermissions.put(name, new PermissionState(permissionState)); + } + } + } + + mGlobalGids = NO_GIDS; + if (other.mGlobalGids != NO_GIDS) { + mGlobalGids = other.mGlobalGids.clone(); + } + + mMissing = other.mMissing; + + mPermissionReviewRequired = other.mPermissionReviewRequired; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final UidPermissionState other = (UidPermissionState) obj; + + synchronized (mLock) { + if (mPermissions == null) { + if (other.mPermissions != null) { + return false; + } + } else if (!mPermissions.equals(other.mPermissions)) { + return false; + } + } + + if (mMissing != other.mMissing) { + return false; + } + + if (mPermissionReviewRequired != other.mPermissionReviewRequired) { + return false; + } + return Arrays.equals(mGlobalGids, other.mGlobalGids); + } + + /** + * Check whether the permissions state is missing for a user. This can happen if permission + * state is rolled back and we'll need to generate a reasonable default state to keep the app + * usable. + */ + public boolean isMissing() { + return mMissing; + } + + /** + * Set whether the permissions state is missing for a user. This can happen if permission state + * is rolled back and we'll need to generate a reasonable default state to keep the app usable. + */ + public void setMissing(boolean missing) { + mMissing = missing; + } + + public boolean isPermissionReviewRequired() { + return mPermissionReviewRequired; + } + + /** + * Gets whether the state has a given permission. + * + * @param name The permission name. + * @return Whether the state has the permission. + */ + public boolean hasPermission(@NonNull String name) { + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + PermissionState permissionState = mPermissions.get(name); + return permissionState != null && permissionState.isGranted(); + } + } + + /** + * Gets whether the state has a given install permission. + * + * @param name The permission name. + * @return Whether the state has the install permission. + */ + public boolean hasInstallPermission(@NonNull String name) { + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + PermissionState permissionState = mPermissions.get(name); + return permissionState != null && permissionState.isGranted() + && !permissionState.isRuntime(); + } + } + + /** + * Returns whether the state has any known request for the given permission name, + * whether or not it has been granted. + * + * @deprecated Not all requested permissions may be here. + */ + @Deprecated + public boolean hasRequestedPermission(@NonNull ArraySet<String> names) { + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + for (int i = names.size() - 1; i >= 0; i--) { + if (mPermissions.get(names.valueAt(i)) != null) { + return true; + } + } + } + + return false; + } + + /** + * Returns whether the state has any known request for the given permission name, + * whether or not it has been granted. + * + * @deprecated Not all requested permissions may be here. + */ + @Deprecated + public boolean hasRequestedPermission(@NonNull String name) { + return mPermissions != null && (mPermissions.get(name) != null); + } + + /** + * Gets all permissions for a given device user id regardless if they + * are install time or runtime permissions. + * + * @return The permissions or an empty set. + */ + @NonNull + public Set<String> getPermissions() { + synchronized (mLock) { + if (mPermissions == null) { + return Collections.emptySet(); + } + + Set<String> permissions = new ArraySet<>(mPermissions.size()); + + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + + if (hasPermission(permission)) { + permissions.add(permission); + } + } + + return permissions; + } + } + + /** + * Gets the flags for a permission. + * + * @param name The permission name. + * @return The permission state or null if no such. + */ + public int getPermissionFlags(@NonNull String name) { + PermissionState permState = getPermissionState(name); + if (permState != null) { + return permState.getFlags(); + } + return 0; + } + + /** + * Update the flags associated with a given permission. + * @param permission The permission whose flags to update. + * @param flagMask Mask for which flags to change. + * @param flagValues New values for the mask flags. + * @return Whether the permission flags changed. + */ + public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask, + int flagValues) { + if (flagMask == 0) { + return false; + } + + PermissionState permissionState = ensurePermissionState(permission); + + final int oldFlags = permissionState.getFlags(); + + synchronized (mLock) { + final boolean updated = permissionState.updateFlags(flagMask, flagValues); + if (updated) { + final int newFlags = permissionState.getFlags(); + if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0 + && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + mPermissionReviewRequired = true; + } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0 + && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) { + if (mPermissionReviewRequired && !hasPermissionRequiringReview()) { + mPermissionReviewRequired = false; + } + } + } + return updated; + } + } + + private boolean hasPermissionRequiringReview() { + synchronized (mLock) { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + final PermissionState permission = mPermissions.valueAt(i); + if ((permission.getFlags() & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + return true; + } + } + } + return false; + } + + public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) { + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + boolean changed = false; + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + PermissionState permissionState = mPermissions.valueAt(i); + changed |= permissionState.updateFlags(flagMask, flagValues); + } + return changed; + } + } + + /** + * Compute the Linux gids for a given device user from the permissions + * granted to this user. Note that these are computed to avoid additional + * state as they are rarely accessed. + * + * @param userId The device user id. + * @return The gids for the device user. + */ + @NonNull + public int[] computeGids(@UserIdInt int userId) { + int[] gids = mGlobalGids; + + synchronized (mLock) { + if (mPermissions != null) { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + PermissionState permissionState = mPermissions.valueAt(i); + if (!permissionState.isGranted()) { + continue; + } + final int[] permGids = permissionState.computeGids(userId); + if (permGids != NO_GIDS) { + gids = appendInts(gids, permGids); + } + } + } + } + + return gids; + } + + /** + * Compute the Linux gids for all device users from the permissions + * granted to these users. + * + * @return The gids for all device users. + */ + @NonNull + public int[] computeGids(@NonNull int[] userIds) { + int[] gids = mGlobalGids; + + for (int userId : userIds) { + final int[] userGids = computeGids(userId); + gids = appendInts(gids, userGids); + } + + return gids; + } + + /** + * Resets the internal state of this object. + */ + public void reset() { + mGlobalGids = NO_GIDS; + + synchronized (mLock) { + mPermissions = null; + invalidateCache(); + } + + mMissing = false; + mPermissionReviewRequired = false; + } + + /** + * Gets the state for a permission or null if no such. + * + * @param name The permission name. + * @return The permission state. + */ + @Nullable + public PermissionState getPermissionState(@NonNull String name) { + synchronized (mLock) { + if (mPermissions == null) { + return null; + } + return mPermissions.get(name); + } + } + + /** + * Gets all permission states. + * + * @return The permission states or an empty set. + */ + @NonNull + public List<PermissionState> getPermissionStates() { + synchronized (mLock) { + if (mPermissions == null) { + return Collections.emptyList(); + } + return new ArrayList<>(mPermissions.values()); + } + } + + /** + * Put a permission state. + */ + public void putPermissionState(@NonNull BasePermission permission, boolean isRuntime, + boolean isGranted, int flags) { + synchronized (mLock) { + ensureNoPermissionState(permission.name); + PermissionState permissionState = ensurePermissionState(permission, isRuntime); + if (isGranted) { + permissionState.grant(); + } + permissionState.updateFlags(flags, flags); + if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + mPermissionReviewRequired = true; + } + } + } + + /** + * Grant a permission. + * + * @param permission The permission to grant. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int grantPermission(@NonNull BasePermission permission) { + if (hasPermission(permission.getName())) { + return PERMISSION_OPERATION_SUCCESS; + } + + PermissionState permissionState = ensurePermissionState(permission); + + if (!permissionState.grant()) { + return PERMISSION_OPERATION_FAILURE; + } + + return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED + : PERMISSION_OPERATION_SUCCESS; + } + + /** + * Revoke a permission. + * + * @param permission The permission to revoke. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokePermission(@NonNull BasePermission permission) { + final String permissionName = permission.getName(); + if (!hasPermission(permissionName)) { + return PERMISSION_OPERATION_SUCCESS; + } + + PermissionState permissionState; + synchronized (mLock) { + permissionState = mPermissions.get(permissionName); + } + + if (!permissionState.revoke()) { + return PERMISSION_OPERATION_FAILURE; + } + + if (permissionState.isDefault()) { + ensureNoPermissionState(permissionName); + } + + return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED + : PERMISSION_OPERATION_SUCCESS; + } + + // TODO: fix this to use arraycopy and append all ints in one go + private static int[] appendInts(int[] current, int[] added) { + if (current != null && added != null) { + for (int guid : added) { + current = ArrayUtils.appendInt(current, guid); + } + } + return current; + } + + @NonNull + private PermissionState ensurePermissionState(@NonNull BasePermission permission) { + return ensurePermissionState(permission, permission.isRuntime()); + } + + @NonNull + private PermissionState ensurePermissionState(@NonNull BasePermission permission, + boolean isRuntime) { + final String permissionName = permission.getName(); + synchronized (mLock) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + PermissionState permissionState = mPermissions.get(permissionName); + if (permissionState == null) { + permissionState = new PermissionState(permission, isRuntime); + mPermissions.put(permissionName, permissionState); + } + return permissionState; + } + } + + private void ensureNoPermissionState(@NonNull String name) { + synchronized (mLock) { + if (mPermissions == null) { + return; + } + mPermissions.remove(name); + if (mPermissions.isEmpty()) { + mPermissions = null; + } + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/UserPermissionState.java b/services/core/java/com/android/server/pm/permission/UserPermissionState.java new file mode 100644 index 000000000000..7f55cb161e40 --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/UserPermissionState.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 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.server.pm.permission; + +import android.annotation.AppIdInt; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +/** + * Permission state for a user. + */ +public final class UserPermissionState { + /** + * Whether the install permissions have been granted to a package, so that no install + * permissions should be added to it unless the package is upgraded. + */ + @GuardedBy("mLock") + @NonNull + private final ArraySet<String> mInstallPermissionsFixed = new ArraySet<>(); + + /** + * Maps from app ID to {@link UidPermissionState}. + */ + @GuardedBy("mLock") + @NonNull + private final SparseArray<UidPermissionState> mUidStates = new SparseArray<>(); + + @NonNull + private final Object mLock; + + public UserPermissionState(@NonNull Object lock) { + mLock = lock; + } + + public boolean areInstallPermissionsFixed(@NonNull String packageName) { + synchronized (mLock) { + return mInstallPermissionsFixed.contains(packageName); + } + } + + public void setInstallPermissionsFixed(@NonNull String packageName, boolean fixed) { + synchronized (mLock) { + if (fixed) { + mInstallPermissionsFixed.add(packageName); + } else { + mInstallPermissionsFixed.remove(packageName); + } + } + } + + @Nullable + public UidPermissionState getUidState(@AppIdInt int appId) { + checkAppId(appId); + synchronized (mLock) { + return mUidStates.get(appId); + } + } + + @NonNull + public UidPermissionState getOrCreateUidState(@AppIdInt int appId) { + checkAppId(appId); + synchronized (mLock) { + UidPermissionState uidState = mUidStates.get(appId); + if (uidState == null) { + uidState = new UidPermissionState(); + mUidStates.put(appId, uidState); + } + return uidState; + } + } + + public void removeUidState(@AppIdInt int appId) { + checkAppId(appId); + synchronized (mLock) { + mUidStates.delete(appId); + } + } + + private void checkAppId(@AppIdInt int appId) { + if (UserHandle.getUserId(appId) != 0) { + throw new IllegalArgumentException(appId + " is not an app ID"); + } + } +} diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index 06c2b1687fa4..523b484269c0 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -25,6 +25,7 @@ import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; +import android.view.InputDevice; import android.view.MotionEvent; import android.view.WindowManagerPolicyConstants.PointerEventListener; @@ -62,8 +63,15 @@ public class TaskTapPointerEventListener implements PointerEventListener { public void onPointerEvent(MotionEvent motionEvent) { switch (motionEvent.getActionMasked()) { case MotionEvent.ACTION_DOWN: { - final int x = (int) motionEvent.getX(); - final int y = (int) motionEvent.getY(); + final int x; + final int y; + if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) { + x = (int) motionEvent.getXCursorPosition(); + y = (int) motionEvent.getYCursorPosition(); + } else { + x = (int) motionEvent.getX(); + y = (int) motionEvent.getY(); + } synchronized (this) { if (!mTouchExcludeRegion.contains(x, y)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 33b12138db79..e85bbd958898 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1560,17 +1560,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ @GuardedBy("getLockObject()") private void migrateToProfileOnOrganizationOwnedDeviceIfCompLocked() { - logIfVerbose("Checking whether we need to migrate COMP "); + if (VERBOSE_LOG) Slog.d(LOG_TAG, "Checking whether we need to migrate COMP "); final int doUserId = mOwners.getDeviceOwnerUserId(); if (doUserId == UserHandle.USER_NULL) { - logIfVerbose("No DO found, skipping migration."); + if (VERBOSE_LOG) Slog.d(LOG_TAG, "No DO found, skipping migration."); return; } final List<UserInfo> profiles = mUserManager.getProfiles(doUserId); if (profiles.size() != 2) { if (profiles.size() == 1) { - logIfVerbose("Profile not found, skipping migration."); + if (VERBOSE_LOG) Slog.d(LOG_TAG, "Profile not found, skipping migration."); } else { Slog.wtf(LOG_TAG, "Found " + profiles.size() + " profiles, skipping migration"); } @@ -1748,7 +1748,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private void applyManagedProfileRestrictionIfDeviceOwnerLocked() { final int doUserId = mOwners.getDeviceOwnerUserId(); if (doUserId == UserHandle.USER_NULL) { - logIfVerbose("No DO found, skipping application of restriction."); + if (VERBOSE_LOG) Slog.d(LOG_TAG, "No DO found, skipping application of restriction."); return; } @@ -8126,7 +8126,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS * permission. */ - private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId, + private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, + @UserIdInt int userId, boolean hasIncompatibleAccountsOrNonAdb) { if (!isAdb()) { enforceCanManageProfileAndDeviceOwners(); @@ -8134,34 +8135,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int code = checkDeviceOwnerProvisioningPreConditionLocked( owner, userId, isAdb(), hasIncompatibleAccountsOrNonAdb); + if (code != CODE_OK) { + throw new IllegalStateException(computeProvisioningErrorString(code, userId)); + } + } + + private static String computeProvisioningErrorString(int code, @UserIdInt int userId) { switch (code) { case CODE_OK: - return; + return "OK"; case CODE_HAS_DEVICE_OWNER: - throw new IllegalStateException( - "Trying to set the device owner, but device owner is already set."); + return "Trying to set the device owner, but device owner is already set."; case CODE_USER_HAS_PROFILE_OWNER: - throw new IllegalStateException("Trying to set the device owner, but the user " - + "already has a profile owner."); + return "Trying to set the device owner, but the user already has a profile owner."; case CODE_USER_NOT_RUNNING: - throw new IllegalStateException("User not running: " + userId); + return "User " + userId + " not running."; case CODE_NOT_SYSTEM_USER: - throw new IllegalStateException("User is not system user"); + return "User " + userId + " is not system user."; case CODE_USER_SETUP_COMPLETED: - throw new IllegalStateException( - "Cannot set the device owner if the device is already set-up"); + return "Cannot set the device owner if the device is already set-up."; case CODE_NONSYSTEM_USER_EXISTS: - throw new IllegalStateException("Not allowed to set the device owner because there " - + "are already several users on the device"); + return "Not allowed to set the device owner because there are already several" + + " users on the device."; case CODE_ACCOUNTS_NOT_EMPTY: - throw new IllegalStateException("Not allowed to set the device owner because there " - + "are already some accounts on the device"); + return "Not allowed to set the device owner because there are already some accounts" + + " on the device."; case CODE_HAS_PAIRED: - throw new IllegalStateException("Not allowed to set the device owner because this " - + "device has already paired"); + return "Not allowed to set the device owner because this device has already " + + "paired."; default: - throw new IllegalStateException("Unexpected @ProvisioningPreCondition " + code); + return "Unexpected @ProvisioningPreCondition: " + code; } + } private void enforceUserUnlocked(int userId) { @@ -11836,11 +11841,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return checkProvisioningPreConditionSkipPermission(action, packageName); } - private int checkProvisioningPreConditionSkipPermission(String action, String packageName) { + private int checkProvisioningPreConditionSkipPermission(String action, + String packageName) { if (!mHasFeature) { + logMissingFeatureAction("Cannot check provisioning for action " + action); return CODE_DEVICE_ADMIN_NOT_SUPPORTED; } + final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName); + if (code != CODE_OK) { + Slog.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName + + ") failed: " + + computeProvisioningErrorString(code, mInjector.userHandleGetCallingUserId())); + } + return code; + } + private int checkProvisioningPreConditionSkipPermissionNoLog(String action, + String packageName) { final int callingUserId = mInjector.userHandleGetCallingUserId(); if (action != null) { switch (action) { @@ -11863,7 +11880,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * except for adb command if no accounts or additional users are present on the device. */ private int checkDeviceOwnerProvisioningPreConditionLocked(@Nullable ComponentName owner, - int deviceOwnerUserId, boolean isAdb, boolean hasIncompatibleAccountsOrNonAdb) { + @UserIdInt int deviceOwnerUserId, boolean isAdb, + boolean hasIncompatibleAccountsOrNonAdb) { if (mOwners.hasDeviceOwner()) { return CODE_HAS_DEVICE_OWNER; } @@ -11908,7 +11926,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private int checkDeviceOwnerProvisioningPreCondition(int deviceOwnerUserId) { + private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int deviceOwnerUserId) { synchronized (getLockObject()) { // hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb. return checkDeviceOwnerProvisioningPreConditionLocked(/* owner unknown */ null, @@ -11917,7 +11935,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private int checkManagedProfileProvisioningPreCondition(String packageName, int callingUserId) { + private int checkManagedProfileProvisioningPreCondition(String packageName, + @UserIdInt int callingUserId) { if (!hasFeatureManagedUsers()) { return CODE_MANAGED_USERS_NOT_SUPPORTED; } @@ -14531,12 +14550,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void logIfVerbose(String message) { - if (VERBOSE_LOG) { - Slog.d(LOG_TAG, message); - } - } - @Override public void setCommonCriteriaModeEnabled(ComponentName who, boolean enabled) { final int userId = mInjector.userHandleGetCallingUserId(); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 82726c7ea20c..5d8f662301c1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -173,6 +173,8 @@ public class MockingOomAdjusterTests { mock(OomAdjProfiler.class)); setFieldValue(ActivityManagerService.class, sService, "mUserController", mock(UserController.class)); + setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", + mock(AppProfiler.class)); doReturn(new ActivityManagerService.ProcessChangeItem()).when(sService) .enqueueProcessChangeItemLocked(anyInt(), anyInt()); sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 3c7206fee9d1..3430dbdce753 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -44,6 +44,7 @@ import static com.android.os.AtomsProto.DNDModeProto.ZEN_MODE_FIELD_NUMBER; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.fail; @@ -1556,6 +1557,49 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(zenRule.getName(), ruleInConfig.name); } + @Test + public void testRulesWithSameUri() { + Uri sharedUri = ZenModeConfig.toScheduleConditionId(new ScheduleInfo()); + AutomaticZenRule zenRule = new AutomaticZenRule("name", + new ComponentName("android", "ScheduleConditionProvider"), + sharedUri, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test"); + AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", + new ComponentName("android", "ScheduleConditionProvider"), + sharedUri, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id2 = mZenModeHelperSpy.addAutomaticZenRule(zenRule2, "test"); + + Condition condition = new Condition(sharedUri, "", Condition.STATE_TRUE); + mZenModeHelperSpy.setAutomaticZenRuleState(sharedUri, condition); + + for (ZenModeConfig.ZenRule rule : mZenModeHelperSpy.mConfig.automaticRules.values()) { + if (rule.id.equals(id)) { + assertNotNull(rule.condition); + assertTrue(rule.condition.state == Condition.STATE_TRUE); + } + if (rule.id.equals(id2)) { + assertNotNull(rule.condition); + assertTrue(rule.condition.state == Condition.STATE_TRUE); + } + } + + condition = new Condition(sharedUri, "", Condition.STATE_FALSE); + mZenModeHelperSpy.setAutomaticZenRuleState(sharedUri, condition); + + for (ZenModeConfig.ZenRule rule : mZenModeHelperSpy.mConfig.automaticRules.values()) { + if (rule.id.equals(id)) { + assertNotNull(rule.condition); + assertTrue(rule.condition.state == Condition.STATE_FALSE); + } + if (rule.id.equals(id2)) { + assertNotNull(rule.condition); + assertTrue(rule.condition.state == Condition.STATE_FALSE); + } + } + } + private void setupZenConfig() { mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowAlarms = false; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 470d4bec4c38..4de1abfee7fc 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2743,32 +2743,27 @@ public class CarrierConfigManager { /** * Indicates if the carrier supports auto-upgrading a call to RTT when receiving a call from a * RTT-supported device. - * @hide */ public static final String KEY_RTT_AUTO_UPGRADE_BOOL = "rtt_auto_upgrade_bool"; /** * Indicates if the carrier supports RTT during a video call. - * @hide */ public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; /** * Indicates if the carrier supports upgrading a voice call to an RTT call during the call. - * @hide */ public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; /** * Indicates if the carrier supports downgrading a RTT call to a voice call during the call. - * @hide */ public static final String KEY_RTT_DOWNGRADE_SUPPORTED_BOOL = "rtt_downgrade_supported_bool"; /** * Indicates if the TTY HCO and VCO options should be hidden in the accessibility menu * if the device is capable of RTT. - * @hide */ public static final String KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL = "hide_tty_hco_vco_with_rtt"; diff --git a/tests/SilkFX/res/layout/activity_glass.xml b/tests/SilkFX/res/layout/activity_glass.xml index a7b76bdeba09..aa09f276d5c8 100644 --- a/tests/SilkFX/res/layout/activity_glass.xml +++ b/tests/SilkFX/res/layout/activity_glass.xml @@ -41,7 +41,16 @@ app:layout_constraintBottom_toTopOf="@+id/bottomPanel" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent"> + <TextView + android:id="@+id/textOverlay" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="18dp" + android:layout_gravity="center" + android:textColor="#ffffff" + android:text="Lorem Ipsum dolor sit amet." /> + </com.android.test.silkfx.materials.GlassView> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/bottomPanel" @@ -90,7 +99,7 @@ android:layout_marginEnd="12dp" android:layout_marginStart="12dp" android:max="150" - android:progress="20" + android:progress="40" app:layout_constraintBottom_toTopOf="@+id/materialOpacityTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" @@ -118,7 +127,7 @@ android:layout_marginEnd="12dp" android:layout_marginBottom="24dp" android:max="100" - android:progress="5" + android:progress="15" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" @@ -141,7 +150,7 @@ android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginBottom="8dp" - android:text="Material Opacity" + android:text="Soft light Opacity" android:textColor="@android:color/white" app:layout_constraintBottom_toTopOf="@+id/materialOpacity" app:layout_constraintStart_toStartOf="parent" /> @@ -219,6 +228,19 @@ app:layout_constraintStart_toEndOf="@+id/background2" android:src="@drawable/background3" /> + <Button + android:id="@+id/pickImage" + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_marginStart="8dp" + android:scaleType="centerCrop" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:clickable="true" + android:onClick="onPickImageClick" + app:layout_constraintBottom_toBottomOf="@+id/background1" + app:layout_constraintStart_toEndOf="@+id/background3" + android:text="Pick file" /> + <Switch android:id="@+id/lightMaterialSwitch" android:layout_width="wrap_content" diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt index 72b342c54d19..dde245ff9baf 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassActivity.kt @@ -16,6 +16,7 @@ package com.android.test.silkfx.materials import android.app.Activity +import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Color @@ -46,8 +47,14 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { lateinit var scrimOpacityValue: TextView lateinit var blurRadiusValue: TextView lateinit var zoomValue: TextView + lateinit var textOverlay: TextView - lateinit var background: Bitmap + var background: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + set(value) { + field = value + backgroundView.setImageBitmap(background) + materialView.backgroundBitmap = background + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,10 +75,9 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { scrimOpacityValue = requireViewById(R.id.scrimOpacityValue) blurRadiusValue = requireViewById(R.id.blurRadiusValue) zoomValue = requireViewById(R.id.zoomValue) + textOverlay = requireViewById(R.id.textOverlay) background = BitmapFactory.decodeResource(resources, R.drawable.background1) - backgroundView.setImageBitmap(background) - materialView.backgroundBitmap = background blurRadiusSeekBar.setOnSeekBarChangeListener(this) materialOpacitySeekBar.setOnSeekBarChangeListener(this) @@ -86,6 +92,7 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { lightMaterialSwitch.setOnCheckedChangeListener { _, isChecked -> materialView.color = if (isChecked) Color.WHITE else Color.BLACK + textOverlay.setTextColor(if (isChecked) Color.BLACK else Color.WHITE) } } @@ -116,6 +123,11 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { } } + override fun onStop() { + super.onStop() + materialView.resetGyroOffsets() + } + override fun onStartTrackingTouch(seekBar: SeekBar?) {} override fun onStopTrackingTouch(seekBar: SeekBar?) {} @@ -128,7 +140,23 @@ class GlassActivity : Activity(), SeekBar.OnSeekBarChangeListener { } background = BitmapFactory.decodeResource(resources, resource) - backgroundView.setImageBitmap(background) - materialView.backgroundBitmap = background + } + + fun onPickImageClick(view: View) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "image/*" + } + startActivityForResult(intent, 0) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode === RESULT_OK) { + data?.data?.also { + contentResolver.openFileDescriptor(it, "r").let { + background = BitmapFactory.decodeFileDescriptor(it?.fileDescriptor) + } + } + } } }
\ No newline at end of file diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt index 60797680b79e..711758476a62 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt @@ -25,23 +25,83 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline import android.graphics.Paint +import android.graphics.RadialGradient import android.graphics.Rect import android.graphics.Shader +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import com.android.internal.graphics.ColorUtils import com.android.test.silkfx.R +import kotlin.math.sin +import kotlin.math.sqrt -class GlassView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) { +class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) { - var noise = BitmapFactory.decodeResource(resources, R.drawable.noise) - var materialPaint = Paint() - var scrimPaint = Paint() - var noisePaint = Paint() - var blurPaint = Paint() + private val textureTranslationMultiplier = 200f - val src = Rect() - val dst = Rect() + private var gyroXRotation = 0f + private var gyroYRotation = 0f + + private var noise = BitmapFactory.decodeResource(resources, R.drawable.noise) + private var materialPaint = Paint() + private var scrimPaint = Paint() + private var noisePaint = Paint() + private var blurPaint = Paint() + + private val src = Rect() + private val dst = Rect() + + private val sensorManager = context.getSystemService(SensorManager::class.java) + private val sensorListener = object : SensorEventListener { + + // Constant to convert nanoseconds to seconds. + private val NS2S = 1.0f / 1000000000.0f + private val EPSILON = 0.000001f + private var timestamp: Float = 0f + + override fun onSensorChanged(event: SensorEvent?) { + // This timestep's delta rotation to be multiplied by the current rotation + // after computing it from the gyro sample data. + if (timestamp != 0f && event != null) { + val dT = (event.timestamp - timestamp) * NS2S + // Axis of the rotation sample, not normalized yet. + var axisX: Float = event.values[0] + var axisY: Float = event.values[1] + var axisZ: Float = event.values[2] + + // Calculate the angular speed of the sample + val omegaMagnitude: Float = sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ) + + // Normalize the rotation vector if it's big enough to get the axis + // (that is, EPSILON should represent your maximum allowable margin of error) + if (omegaMagnitude > EPSILON) { + axisX /= omegaMagnitude + axisY /= omegaMagnitude + axisZ /= omegaMagnitude + } + + // Integrate around this axis with the angular speed by the timestep + // in order to get a delta rotation from this sample over the timestep + // We will convert this axis-angle representation of the delta rotation + // into a quaternion before turning it into the rotation matrix. + val thetaOverTwo: Float = omegaMagnitude * dT / 2.0f + val sinThetaOverTwo: Float = sin(thetaOverTwo) + gyroXRotation += sinThetaOverTwo * axisX + gyroYRotation += sinThetaOverTwo * axisY + + invalidate() + } + timestamp = event?.timestamp?.toFloat() ?: 0f + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { } + } var backgroundBitmap: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) set(value) { @@ -97,6 +157,7 @@ class GlassView(context: Context, attributeSet: AttributeSet) : View(context, at } init { + setWillNotDraw(false) materialPaint.blendMode = BlendMode.SOFT_LIGHT noisePaint.blendMode = BlendMode.SOFT_LIGHT noisePaint.shader = BitmapShader(noise, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) @@ -112,12 +173,25 @@ class GlassView(context: Context, attributeSet: AttributeSet) : View(context, at clipToOutline = true } + override fun onAttachedToWindow() { + sensorManager?.getSensorList(Sensor.TYPE_GYROSCOPE)?.firstOrNull().let { + sensorManager?.registerListener(sensorListener, it, SensorManager.SENSOR_DELAY_GAME) + } + } + + override fun onDetachedFromWindow() { + sensorManager?.unregisterListener(sensorListener) + } + override fun onDraw(canvas: Canvas?) { - src.set(-width/2, -height/2, width/2, height/2) + src.set(-width / 2, -height / 2, width / 2, height / 2) src.scale(1.0f + zoom) val centerX = left + width / 2 val centerY = top + height / 2 - src.set(src.left + centerX, src.top + centerY, src.right + centerX, src.bottom + centerY) + val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt() + val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt() + src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset, + src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset) dst.set(0, 0, width, height) canvas?.drawBitmap(backgroundBitmap, src, dst, blurPaint) @@ -125,4 +199,10 @@ class GlassView(context: Context, attributeSet: AttributeSet) : View(context, at canvas?.drawRect(dst, noisePaint) canvas?.drawRect(dst, scrimPaint) } + + fun resetGyroOffsets() { + gyroXRotation = 0f + gyroYRotation = 0f + invalidate() + } }
\ No newline at end of file |