diff options
400 files changed, 11256 insertions, 6669 deletions
diff --git a/Android.bp b/Android.bp index 7d02d7d50f84..ee5db70b3ce1 100644 --- a/Android.bp +++ b/Android.bp @@ -109,6 +109,7 @@ filegroup { ":platform-compat-native-aidl", // AIDL sources from external directories + ":android.hardware.graphics.common-V3-java-source", ":android.hardware.security.keymint-V1-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.tv.tuner-V1-java-source", @@ -288,6 +289,7 @@ java_defaults { // TODO: remove when moved to the below package "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", "packages/modules/Connectivity/framework/aidl-export", + "hardware/interfaces/graphics/common/aidl", ], }, dxflags: [ @@ -537,6 +539,7 @@ stubs_defaults { // TODO: remove when moved to the below package "frameworks/base/packages/ConnectivityT/framework-t/aidl-export", "packages/modules/Connectivity/framework/aidl-export", + "hardware/interfaces/graphics/common/aidl", ], }, // These are libs from framework-internal-utils that are required (i.e. being referenced) diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index c706a3a54c38..823738347bac 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -25,6 +25,7 @@ import static android.os.Process.INVALID_UID; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -127,28 +128,47 @@ import java.util.stream.Collectors; <pre> digraph { + subgraph cluster_legend { + label="Legend" + + wakeup_alarm [label="Entering this state requires a wakeup alarm",color=red,shape=box] + nonwakeup_alarm [ + label="This state can be entered from a non-wakeup alarm",color=blue,shape=oval + ] + no_alarm [label="This state doesn't require an alarm",color=black,shape=diamond] + } + subgraph deep { label="deep"; - STATE_ACTIVE [label="STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon"] - STATE_INACTIVE [label="STATE_INACTIVE\nScreen off AND Not charging"] + STATE_ACTIVE [ + label="STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon", + color=black,shape=diamond + ] + STATE_INACTIVE [ + label="STATE_INACTIVE\nScreen off AND Not charging",color=black,shape=diamond + ] STATE_QUICK_DOZE_DELAY [ label="STATE_QUICK_DOZE_DELAY\n" + "Screen off AND Not charging\n" - + "Location, motion detection, and significant motion monitoring turned off" + + "Location, motion detection, and significant motion monitoring turned off", + color=black,shape=diamond ] STATE_IDLE_PENDING [ - label="STATE_IDLE_PENDING\nSignificant motion monitoring turned on" + label="STATE_IDLE_PENDING\nSignificant motion monitoring turned on", + color=red,shape=box ] - STATE_SENSING [label="STATE_SENSING\nMonitoring for ANY motion"] + STATE_SENSING [label="STATE_SENSING\nMonitoring for ANY motion",color=red,shape=box] STATE_LOCATING [ - label="STATE_LOCATING\nRequesting location, motion monitoring still on" + label="STATE_LOCATING\nRequesting location, motion monitoring still on", + color=red,shape=box ] STATE_IDLE [ label="STATE_IDLE\nLocation and motion detection turned off\n" - + "Significant motion monitoring state unchanged" + + "Significant motion monitoring state unchanged", + color=red,shape=box ] - STATE_IDLE_MAINTENANCE [label="STATE_IDLE_MAINTENANCE\n"] + STATE_IDLE_MAINTENANCE [label="STATE_IDLE_MAINTENANCE\n",color=red,shape=box] STATE_ACTIVE -> STATE_INACTIVE [ label="becomeInactiveIfAppropriateLocked() AND Quick Doze not enabled" @@ -213,19 +233,22 @@ import java.util.stream.Collectors; label="light" LIGHT_STATE_ACTIVE [ - label="LIGHT_STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon" + label="LIGHT_STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon", + color=black,shape=diamond ] - LIGHT_STATE_INACTIVE [label="LIGHT_STATE_INACTIVE\nScreen off AND Not charging"] - LIGHT_STATE_PRE_IDLE [ - label="LIGHT_STATE_PRE_IDLE\n" - + "Delay going into LIGHT_STATE_IDLE due to some running jobs or alarms" + LIGHT_STATE_INACTIVE [ + label="LIGHT_STATE_INACTIVE\nScreen off AND Not charging", + color=black,shape=diamond ] - LIGHT_STATE_IDLE [label="LIGHT_STATE_IDLE\n"] + LIGHT_STATE_IDLE [label="LIGHT_STATE_IDLE\n",color=blue,shape=oval] LIGHT_STATE_WAITING_FOR_NETWORK [ label="LIGHT_STATE_WAITING_FOR_NETWORK\n" - + "Coming out of LIGHT_STATE_IDLE, waiting for network" + + "Coming out of LIGHT_STATE_IDLE, waiting for network", + color=black,shape=diamond + ] + LIGHT_STATE_IDLE_MAINTENANCE [ + label="LIGHT_STATE_IDLE_MAINTENANCE\n",color=red,shape=box ] - LIGHT_STATE_IDLE_MAINTENANCE [label="LIGHT_STATE_IDLE_MAINTENANCE\n"] LIGHT_STATE_OVERRIDE [ label="LIGHT_STATE_OVERRIDE\nDevice in deep doze, light no longer changing states" ] @@ -236,16 +259,9 @@ import java.util.stream.Collectors; LIGHT_STATE_ACTIVE -> LIGHT_STATE_OVERRIDE [label="deep goes to STATE_IDLE"] LIGHT_STATE_INACTIVE -> LIGHT_STATE_ACTIVE [label="becomeActiveLocked()"] - LIGHT_STATE_INACTIVE -> LIGHT_STATE_PRE_IDLE [label="active jobs"] - LIGHT_STATE_INACTIVE -> LIGHT_STATE_IDLE [label="no active jobs"] + LIGHT_STATE_INACTIVE -> LIGHT_STATE_IDLE [label="some time transpires"] LIGHT_STATE_INACTIVE -> LIGHT_STATE_OVERRIDE [label="deep goes to STATE_IDLE"] - LIGHT_STATE_PRE_IDLE -> LIGHT_STATE_ACTIVE [label="becomeActiveLocked()"] - LIGHT_STATE_PRE_IDLE -> LIGHT_STATE_IDLE [ - label="stepLightIdleStateLocked(), exitMaintenanceEarlyIfNeededLocked()" - ] - LIGHT_STATE_PRE_IDLE -> LIGHT_STATE_OVERRIDE [label="deep goes to STATE_IDLE"] - LIGHT_STATE_IDLE -> LIGHT_STATE_ACTIVE [label="becomeActiveLocked()"] LIGHT_STATE_IDLE -> LIGHT_STATE_WAITING_FOR_NETWORK [label="no network"] LIGHT_STATE_IDLE -> LIGHT_STATE_IDLE_MAINTENANCE @@ -421,9 +437,6 @@ public class DeviceIdleController extends SystemService /** Device is inactive (screen off) and we are waiting to for the first light idle. */ @VisibleForTesting static final int LIGHT_STATE_INACTIVE = 1; - /** Device is about to go idle for the first time, wait for current work to complete. */ - @VisibleForTesting - static final int LIGHT_STATE_PRE_IDLE = 3; /** Device is in the light idle state, trying to stay asleep as much as possible. */ @VisibleForTesting static final int LIGHT_STATE_IDLE = 4; @@ -434,7 +447,7 @@ public class DeviceIdleController extends SystemService /** Device is in the light idle state, but temporarily out of idle to do regular maintenance. */ @VisibleForTesting static final int LIGHT_STATE_IDLE_MAINTENANCE = 6; - /** Device light idle state is overriden, now applying deep doze state. */ + /** Device light idle state is overridden, now applying deep doze state. */ @VisibleForTesting static final int LIGHT_STATE_OVERRIDE = 7; @@ -443,7 +456,6 @@ public class DeviceIdleController extends SystemService switch (state) { case LIGHT_STATE_ACTIVE: return "ACTIVE"; case LIGHT_STATE_INACTIVE: return "INACTIVE"; - case LIGHT_STATE_PRE_IDLE: return "PRE_IDLE"; case LIGHT_STATE_IDLE: return "IDLE"; case LIGHT_STATE_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK"; case LIGHT_STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE"; @@ -468,10 +480,10 @@ public class DeviceIdleController extends SystemService @GuardedBy("this") private long mNextLightIdleDelay; @GuardedBy("this") - private long mNextLightIdleDelayFlex; - @GuardedBy("this") private long mNextLightAlarmTime; @GuardedBy("this") + private long mNextLightMaintenanceAlarmTime; + @GuardedBy("this") private long mNextSensingTimeoutAlarmTime; /** How long a light idle maintenance window should last. */ @@ -658,13 +670,21 @@ public class DeviceIdleController extends SystemService } }; - private final AlarmManager.OnAlarmListener mLightAlarmListener - = new AlarmManager.OnAlarmListener() { - @Override - public void onAlarm() { - synchronized (DeviceIdleController.this) { - stepLightIdleStateLocked("s:alarm"); - } + private final AlarmManager.OnAlarmListener mLightAlarmListener = () -> { + if (DEBUG) { + Slog.d(TAG, "Light progression alarm fired"); + } + synchronized (DeviceIdleController.this) { + stepLightIdleStateLocked("s:alarm"); + } + }; + + private final AlarmManager.OnAlarmListener mLightMaintenanceAlarmListener = () -> { + if (DEBUG) { + Slog.d(TAG, "Light maintenance alarm fired"); + } + synchronized (DeviceIdleController.this) { + stepLightIdleStateLocked("s:alarm"); } }; @@ -928,11 +948,7 @@ public class DeviceIdleController extends SystemService private static final String KEY_FLEX_TIME_SHORT = "flex_time_short"; private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = "light_after_inactive_to"; - private static final String KEY_LIGHT_PRE_IDLE_TIMEOUT = "light_pre_idle_to"; private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to"; - private static final String KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = - "light_idle_to_initial_flex"; - private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX = "light_max_idle_to_flex"; private static final String KEY_LIGHT_IDLE_FACTOR = "light_idle_factor"; private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT = "light_max_idle_to"; private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = @@ -978,15 +994,9 @@ public class DeviceIdleController extends SystemService private static final long DEFAULT_FLEX_TIME_SHORT = !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; private static final long DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = - !COMPRESS_TIME ? 60 * 1000L : 15 * 1000L; - private static final long DEFAULT_LIGHT_PRE_IDLE_TIMEOUT = - !COMPRESS_TIME ? 3 * 60 * 1000L : 30 * 1000L; + !COMPRESS_TIME ? 4 * 60 * 1000L : 30 * 1000L; private static final long DEFAULT_LIGHT_IDLE_TIMEOUT = !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L; - private static final long DEFAULT_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = - !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L; - private static final long DEFAULT_LIGHT_MAX_IDLE_TIMEOUT_FLEX = - !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L; private static final float DEFAULT_LIGHT_IDLE_FACTOR = 2f; private static final long DEFAULT_LIGHT_MAX_IDLE_TIMEOUT = !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L; @@ -1054,15 +1064,6 @@ public class DeviceIdleController extends SystemService public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT; /** - * This is amount of time we will wait from the point where we decide we would - * like to go idle until we actually do, while waiting for jobs and other current - * activity to finish. - * - * @see #KEY_LIGHT_PRE_IDLE_TIMEOUT - */ - public long LIGHT_PRE_IDLE_TIMEOUT = DEFAULT_LIGHT_PRE_IDLE_TIMEOUT; - - /** * This is the initial time that we will run in light idle maintenance mode. * * @see #KEY_LIGHT_IDLE_TIMEOUT @@ -1070,21 +1071,6 @@ public class DeviceIdleController extends SystemService public long LIGHT_IDLE_TIMEOUT = DEFAULT_LIGHT_IDLE_TIMEOUT; /** - * This is the initial alarm window size that we will tolerate for light idle maintenance - * timing. - * - * @see #KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX - */ - public long LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = DEFAULT_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX; - - /** - * This is the maximum value that {@link #LIGHT_IDLE_TIMEOUT_INITIAL_FLEX} should take. - * - * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX - */ - public long LIGHT_MAX_IDLE_TIMEOUT_FLEX = DEFAULT_LIGHT_MAX_IDLE_TIMEOUT_FLEX; - - /** * Scaling factor to apply to the light idle mode time each time we complete a cycle. * * @see #KEY_LIGHT_IDLE_FACTOR @@ -1327,24 +1313,10 @@ public class DeviceIdleController extends SystemService KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT); break; - case KEY_LIGHT_PRE_IDLE_TIMEOUT: - LIGHT_PRE_IDLE_TIMEOUT = properties.getLong( - KEY_LIGHT_PRE_IDLE_TIMEOUT, DEFAULT_LIGHT_PRE_IDLE_TIMEOUT); - break; case KEY_LIGHT_IDLE_TIMEOUT: LIGHT_IDLE_TIMEOUT = properties.getLong( KEY_LIGHT_IDLE_TIMEOUT, DEFAULT_LIGHT_IDLE_TIMEOUT); break; - case KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX: - LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = properties.getLong( - KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX, - DEFAULT_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX); - break; - case KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX: - LIGHT_MAX_IDLE_TIMEOUT_FLEX = properties.getLong( - KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX, - DEFAULT_LIGHT_MAX_IDLE_TIMEOUT_FLEX); - break; case KEY_LIGHT_IDLE_FACTOR: LIGHT_IDLE_FACTOR = Math.max(1, properties.getFloat( KEY_LIGHT_IDLE_FACTOR, DEFAULT_LIGHT_IDLE_FACTOR)); @@ -1497,22 +1469,10 @@ public class DeviceIdleController extends SystemService TimeUtils.formatDuration(LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, pw); pw.println(); - pw.print(" "); pw.print(KEY_LIGHT_PRE_IDLE_TIMEOUT); pw.print("="); - TimeUtils.formatDuration(LIGHT_PRE_IDLE_TIMEOUT, pw); - pw.println(); - pw.print(" "); pw.print(KEY_LIGHT_IDLE_TIMEOUT); pw.print("="); TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw); pw.println(); - pw.print(" "); pw.print(KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX); pw.print("="); - TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT_INITIAL_FLEX, pw); - pw.println(); - - pw.print(" "); pw.print(KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX); pw.print("="); - TimeUtils.formatDuration(LIGHT_MAX_IDLE_TIMEOUT_FLEX, pw); - pw.println(); - pw.print(" "); pw.print(KEY_LIGHT_IDLE_FACTOR); pw.print("="); pw.print(LIGHT_IDLE_FACTOR); pw.println(); @@ -3088,7 +3048,7 @@ public class DeviceIdleController extends SystemService if (conn != mNetworkConnected) { mNetworkConnected = conn; if (conn && mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) { - stepLightIdleStateLocked("network"); + stepLightIdleStateLocked("network", /* forceProgression */ true); } } } @@ -3343,7 +3303,11 @@ public class DeviceIdleController extends SystemService if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE"); resetLightIdleManagementLocked(); scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, - mConstants.FLEX_TIME_SHORT, true); + mConstants.FLEX_TIME_SHORT); + // After moving in INACTIVE, the maintenance window should start the time inactive + // timeout and a single light idle period. + scheduleLightMaintenanceAlarmLocked( + mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.LIGHT_IDLE_TIMEOUT); EventLogTags.writeDeviceIdleLight(mLightState, "no activity"); } } @@ -3364,9 +3328,9 @@ public class DeviceIdleController extends SystemService @GuardedBy("this") private void resetLightIdleManagementLocked() { - mNextLightIdleDelay = 0; - mNextLightIdleDelayFlex = 0; - mCurLightIdleBudget = 0; + mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT; + mMaintenanceStartTime = 0; + mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; cancelLightAlarmLocked(); } @@ -3401,90 +3365,115 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") - void stepLightIdleStateLocked(String reason) { - if (mLightState == LIGHT_STATE_OVERRIDE) { + private void stepLightIdleStateLocked(String reason) { + stepLightIdleStateLocked(reason, false); + } + + @GuardedBy("this") + @VisibleForTesting + @SuppressLint("WakelockTimeout") + void stepLightIdleStateLocked(String reason, boolean forceProgression) { + if (mLightState == LIGHT_STATE_ACTIVE || mLightState == LIGHT_STATE_OVERRIDE) { // If we are already in deep device idle mode, then // there is nothing left to do for light mode. return; } - if (DEBUG) Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + mLightState); + if (DEBUG) { + Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + lightStateToString(mLightState) + + " force=" + forceProgression); + } EventLogTags.writeDeviceIdleLightStep(); - switch (mLightState) { - case LIGHT_STATE_INACTIVE: - mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; - // Reset the upcoming idle delays. - mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT; - mNextLightIdleDelayFlex = mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX; - mMaintenanceStartTime = 0; - if (!isOpsInactiveLocked()) { - // We have some active ops going on... give them a chance to finish - // before going in to our first idle. - mLightState = LIGHT_STATE_PRE_IDLE; - EventLogTags.writeDeviceIdleLight(mLightState, reason); - scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT, - mConstants.FLEX_TIME_SHORT, true); - break; - } - // Nothing active, fall through to immediately idle. - case LIGHT_STATE_PRE_IDLE: - case LIGHT_STATE_IDLE_MAINTENANCE: - if (mMaintenanceStartTime != 0) { - long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime; - if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) { - // We didn't use up all of our minimum budget; add this to the reserve. - mCurLightIdleBudget += - (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET - duration); - } else { - // We used more than our minimum budget; this comes out of the reserve. - mCurLightIdleBudget -= - (duration - mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); - } + final long nowElapsed = mInjector.getElapsedRealtime(); + final boolean crossedMaintenanceTime = + mNextLightMaintenanceAlarmTime > 0 && nowElapsed >= mNextLightMaintenanceAlarmTime; + final boolean crossedProgressionTime = + mNextLightAlarmTime > 0 && nowElapsed >= mNextLightAlarmTime; + final boolean enterMaintenance; + if (crossedMaintenanceTime) { + if (crossedProgressionTime) { + enterMaintenance = (mNextLightAlarmTime <= mNextLightMaintenanceAlarmTime); + } else { + enterMaintenance = true; + } + } else if (crossedProgressionTime) { + enterMaintenance = false; + } else if (forceProgression) { + // This will happen for adb commands, unit tests, + // and when we're in WAITING_FOR_NETWORK and the network connects. + enterMaintenance = + mLightState == LIGHT_STATE_IDLE + || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK; + } else { + Slog.wtfStack(TAG, "stepLightIdleStateLocked called in invalid state"); + return; + } + + if (enterMaintenance) { + if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) { + // We have been idling long enough, now it is time to do some work. + mActiveIdleOpCount = 1; + mActiveIdleWakeLock.acquire(); + mMaintenanceStartTime = SystemClock.elapsedRealtime(); + if (mCurLightIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) { + mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; + } else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) { + mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET; } - mMaintenanceStartTime = 0; - scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex, false); mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT, (long) (mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR)); - mNextLightIdleDelayFlex = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT_FLEX, - (long) (mNextLightIdleDelayFlex * mConstants.LIGHT_IDLE_FACTOR)); - if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE."); - mLightState = LIGHT_STATE_IDLE; + // We're entering MAINTENANCE. It should end curLightIdleBudget time from now. + // The next maintenance window should be curLightIdleBudget + nextLightIdleDelay + // time from now. + scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT); + scheduleLightMaintenanceAlarmLocked(mCurLightIdleBudget + mNextLightIdleDelay); + if (DEBUG) { + Slog.d(TAG, "Moved from " + lightStateToString(mLightState) + + " to LIGHT_STATE_IDLE_MAINTENANCE"); + } + mLightState = LIGHT_STATE_IDLE_MAINTENANCE; EventLogTags.writeDeviceIdleLight(mLightState, reason); - addEvent(EVENT_LIGHT_IDLE, null); - mGoingIdleWakeLock.acquire(); - mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT); - break; - case LIGHT_STATE_IDLE: - case LIGHT_STATE_WAITING_FOR_NETWORK: - if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) { - // We have been idling long enough, now it is time to do some work. - mActiveIdleOpCount = 1; - mActiveIdleWakeLock.acquire(); - mMaintenanceStartTime = SystemClock.elapsedRealtime(); - if (mCurLightIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) { - mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; - } else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) { - mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET; - } - scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT, true); - if (DEBUG) Slog.d(TAG, - "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE."); - mLightState = LIGHT_STATE_IDLE_MAINTENANCE; - EventLogTags.writeDeviceIdleLight(mLightState, reason); - addEvent(EVENT_LIGHT_MAINTENANCE, null); - mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); + addEvent(EVENT_LIGHT_MAINTENANCE, null); + mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); + } else { + // We'd like to do maintenance, but currently don't have network + // connectivity... let's try to wait until the network comes back. + // We'll only wait for another full idle period, however, and then give up. + scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay); + mNextLightAlarmTime = 0; + if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK."); + mLightState = LIGHT_STATE_WAITING_FOR_NETWORK; + EventLogTags.writeDeviceIdleLight(mLightState, reason); + } + } else { + if (mMaintenanceStartTime != 0) { + // Cap duration at budget since the non-wakeup alarm to exit maintenance may + // not fire at the exact intended time, but once the system is up, we will stop + // more ongoing work. + long duration = Math.min(mCurLightIdleBudget, + SystemClock.elapsedRealtime() - mMaintenanceStartTime); + if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) { + // We didn't use up all of our minimum budget; add this to the reserve. + mCurLightIdleBudget += + (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET - duration); } else { - // We'd like to do maintenance, but currently don't have network - // connectivity... let's try to wait until the network comes back. - // We'll only wait for another full idle period, however, and then give up. - scheduleLightAlarmLocked(mNextLightIdleDelay, - mNextLightIdleDelayFlex / 2, true); - if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK."); - mLightState = LIGHT_STATE_WAITING_FOR_NETWORK; - EventLogTags.writeDeviceIdleLight(mLightState, reason); + // We used more than our minimum budget; this comes out of the reserve. + mCurLightIdleBudget -= + (duration - mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); } - break; + } + mMaintenanceStartTime = 0; + // We're entering IDLE. We may have used less than curLightIdleBudget for the + // maintenance window, so reschedule the alarm starting from now. + scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay); + mNextLightAlarmTime = 0; + if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE."); + mLightState = LIGHT_STATE_IDLE; + EventLogTags.writeDeviceIdleLight(mLightState, reason); + addEvent(EVENT_LIGHT_IDLE, null); + mGoingIdleWakeLock.acquire(); + mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT); } } @@ -3839,8 +3828,7 @@ public class DeviceIdleController extends SystemService @GuardedBy("this") void exitMaintenanceEarlyIfNeededLocked() { - if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE - || mLightState == LIGHT_STATE_PRE_IDLE) { + if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) { if (isOpsInactiveLocked()) { final long now = SystemClock.elapsedRealtime(); if (DEBUG) { @@ -3853,10 +3841,8 @@ public class DeviceIdleController extends SystemService } if (mState == STATE_IDLE_MAINTENANCE) { stepIdleStateLocked("s:early"); - } else if (mLightState == LIGHT_STATE_PRE_IDLE) { - stepLightIdleStateLocked("s:predone"); } else { - stepLightIdleStateLocked("s:early"); + stepLightIdleStateLocked("s:early", /* forceProgression */ true); } } } @@ -3969,6 +3955,10 @@ public class DeviceIdleController extends SystemService mNextLightAlarmTime = 0; mAlarmManager.cancel(mLightAlarmListener); } + if (mNextLightMaintenanceAlarmTime != 0) { + mNextLightMaintenanceAlarmTime = 0; + mAlarmManager.cancel(mLightMaintenanceAlarmListener); + } } @GuardedBy("this") @@ -4035,26 +4025,54 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") - void scheduleLightAlarmLocked(long delay, long flex, boolean wakeup) { + @VisibleForTesting + void scheduleLightAlarmLocked(long delay, long flex) { if (DEBUG) { Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "") - + ", wakeup=" + wakeup + ")"); + + ")"); } - mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay; + mNextLightAlarmTime = mInjector.getElapsedRealtime() + delay; if (mConstants.USE_WINDOW_ALARMS) { mAlarmManager.setWindow( - wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME, + AlarmManager.ELAPSED_REALTIME, mNextLightAlarmTime, flex, "DeviceIdleController.light", mLightAlarmListener, mHandler); } else { mAlarmManager.set( - wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME, + AlarmManager.ELAPSED_REALTIME, mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler); } } + @GuardedBy("this") + @VisibleForTesting + void scheduleLightMaintenanceAlarmLocked(long delay) { + if (DEBUG) { + Slog.d(TAG, "scheduleLightMaintenanceAlarmLocked(" + delay + ")"); + } + mNextLightMaintenanceAlarmTime = mInjector.getElapsedRealtime() + delay; + mAlarmManager.setWindow( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + mNextLightMaintenanceAlarmTime, mConstants.FLEX_TIME_SHORT, + "DeviceIdleController.light", mLightMaintenanceAlarmListener, mHandler); + } + + @VisibleForTesting + long getNextLightAlarmTimeForTesting() { + synchronized (this) { + return mNextLightAlarmTime; + } + } + + @VisibleForTesting + long getNextLightMaintenanceAlarmTimeForTesting() { + synchronized (this) { + return mNextLightMaintenanceAlarmTime; + } + } + private void scheduleMotionRegistrationAlarmLocked() { if (DEBUG) Slog.d(TAG, "scheduleMotionRegistrationAlarmLocked"); long nextMotionRegistrationAlarmTime = @@ -4424,7 +4442,7 @@ public class DeviceIdleController extends SystemService pw.print("Stepped to deep: "); pw.println(stateToString(mState)); } else if ("light".equals(arg)) { - stepLightIdleStateLocked("s:shell"); + stepLightIdleStateLocked("s:shell", /* forceProgression */ true); pw.print("Stepped to light: "); pw.println(lightStateToString(mLightState)); } else { pw.println("Unknown idle mode: " + arg); @@ -4464,7 +4482,7 @@ public class DeviceIdleController extends SystemService becomeInactiveIfAppropriateLocked(); int curLightState = mLightState; while (curLightState != LIGHT_STATE_IDLE) { - stepLightIdleStateLocked("s:shell"); + stepLightIdleStateLocked("s:shell", /* forceProgression */ true); if (curLightState == mLightState) { pw.print("Unable to go light idle; stopped at "); pw.println(lightStateToString(mLightState)); @@ -5076,19 +5094,19 @@ public class DeviceIdleController extends SystemService if (mNextLightIdleDelay != 0) { pw.print(" mNextLightIdleDelay="); TimeUtils.formatDuration(mNextLightIdleDelay, pw); - if (mConstants.USE_WINDOW_ALARMS) { - pw.print(" (flex="); - TimeUtils.formatDuration(mNextLightIdleDelayFlex, pw); - pw.println(")"); - } else { - pw.println(); - } + pw.println(); } if (mNextLightAlarmTime != 0) { pw.print(" mNextLightAlarmTime="); TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw); pw.println(); } + if (mNextLightMaintenanceAlarmTime != 0) { + pw.print(" mNextLightMaintenanceAlarmTime="); + TimeUtils.formatDuration( + mNextLightMaintenanceAlarmTime, SystemClock.elapsedRealtime(), pw); + pw.println(); + } if (mCurLightIdleBudget != 0) { pw.print(" mCurLightIdleBudget="); TimeUtils.formatDuration(mCurLightIdleBudget, pw); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 1620983c4137..a5c2bcc2d9e2 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -72,6 +72,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.PermissionChecker; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; @@ -1839,6 +1840,9 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(a.packageName, UserHandle.getUserId(a.uid))) { return false; } + if (hasUseExactAlarmPermission(a.packageName, a.uid)) { + return false; + } return !isExemptFromExactAlarmPermission(a.uid); }; removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); @@ -1900,6 +1904,9 @@ public class AlarmManagerService extends SystemService { || !isExactAlarmChangeEnabled(packageName, userId)) { return; } + if (hasUseExactAlarmPermission(packageName, uid)) { + return; + } final boolean requested = mExactAlarmCandidates.contains( UserHandle.getAppId(uid)); @@ -2534,6 +2541,8 @@ public class AlarmManagerService extends SystemService { private static boolean getScheduleExactAlarmState(boolean requested, boolean denyListed, int appOpMode) { + // This does not account for the state of the USE_EXACT_ALARM permission. + // The caller should do that separately. if (!requested) { return false; } @@ -2543,7 +2552,16 @@ public class AlarmManagerService extends SystemService { return appOpMode == AppOpsManager.MODE_ALLOWED; } + boolean hasUseExactAlarmPermission(String packageName, int uid) { + return PermissionChecker.checkPermissionForPreflight(getContext(), + Manifest.permission.USE_EXACT_ALARM, PermissionChecker.PID_UNKNOWN, uid, + packageName) == PermissionChecker.PERMISSION_GRANTED; + } + boolean hasScheduleExactAlarmInternal(String packageName, int uid) { + if (hasUseExactAlarmPermission(packageName, uid)) { + return true; + } // Not using getScheduleExactAlarmState as this can avoid some calls to AppOpsService. // Not using #mLastOpScheduleExactAlarm as it may contain stale values. // No locking needed as all internal containers being queried are immutable. @@ -3759,6 +3777,9 @@ public class AlarmManagerService extends SystemService { if (!isExactAlarmChangeEnabled(changedPackage, userId)) { continue; } + if (hasUseExactAlarmPermission(changedPackage, uid)) { + continue; + } final int appOpMode; synchronized (mLock) { appOpMode = mLastOpScheduleExactAlarm.get(uid, @@ -3778,7 +3799,8 @@ public class AlarmManagerService extends SystemService { } if (added) { synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, changedPackage); + removeExactAlarmsOnPermissionRevokedLocked(uid, + changedPackage, /*killUid = */ true); } } else { sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); @@ -3794,7 +3816,7 @@ public class AlarmManagerService extends SystemService { * This is not expected to get called frequently. */ @GuardedBy("mLock") - void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName) { + void removeExactAlarmsOnPermissionRevokedLocked(int uid, String packageName, boolean killUid) { if (isExemptFromExactAlarmPermission(uid) || !isExactAlarmChangeEnabled(packageName, UserHandle.getUserId(uid))) { return; @@ -3805,7 +3827,7 @@ public class AlarmManagerService extends SystemService { && a.windowLength == 0); removeAlarmsInternalLocked(whichAlarms, REMOVE_REASON_EXACT_PERMISSION_REVOKED); - if (mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { + if (killUid && mConstants.KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED) { PermissionManagerService.killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid), "schedule_exact_alarm revoked"); } @@ -4617,6 +4639,7 @@ public class AlarmManagerService extends SystemService { public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11; public static final int TARE_AFFORDABILITY_CHANGED = 12; + public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; AlarmHandler() { super(Looper.myLooper()); @@ -4715,10 +4738,11 @@ public class AlarmManagerService extends SystemService { break; case REMOVE_EXACT_ALARMS: - final int uid = msg.arg1; - final String packageName = (String) msg.obj; + int uid = msg.arg1; + String packageName = (String) msg.obj; synchronized (mLock) { - removeExactAlarmsOnPermissionRevokedLocked(uid, packageName); + removeExactAlarmsOnPermissionRevokedLocked(uid, packageName, /*killUid = */ + true); } break; case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: @@ -4730,6 +4754,16 @@ public class AlarmManagerService extends SystemService { case REFRESH_EXACT_ALARM_CANDIDATES: refreshExactAlarmCandidates(); break; + case CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE: + packageName = (String) msg.obj; + uid = msg.arg1; + if (!hasScheduleExactAlarmInternal(packageName, uid)) { + synchronized (mLock) { + removeExactAlarmsOnPermissionRevokedLocked(uid, + packageName, /*killUid = */false); + } + } + break; default: // nope, just ignore it break; @@ -4914,6 +4948,12 @@ public class AlarmManagerService extends SystemService { } break; case Intent.ACTION_PACKAGE_ADDED: + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + final String packageUpdated = intent.getData().getSchemeSpecificPart(); + mHandler.obtainMessage( + AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE, uid, -1, + packageUpdated).sendToTarget(); + } mHandler.sendEmptyMessage(AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES); return; } diff --git a/core/api/current.txt b/core/api/current.txt index 8a5475901896..723104778fea 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -111,6 +111,7 @@ package android { field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA"; field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS"; field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS"; + field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN"; field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR"; field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL"; field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS"; @@ -121,6 +122,7 @@ package android { field public static final String NFC = "android.permission.NFC"; field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO"; field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT"; + field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS"; @@ -192,6 +194,7 @@ package android { field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"; field public static final String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC"; + field public static final String USE_EXACT_ALARM = "android.permission.USE_EXACT_ALARM"; field @Deprecated public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; field public static final String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT"; field public static final String USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"; @@ -1745,6 +1748,7 @@ package android { field public static final int windowSplashScreenAnimatedIcon = 16844333; // 0x101062d field public static final int windowSplashScreenAnimationDuration = 16844334; // 0x101062e field public static final int windowSplashScreenBackground = 16844332; // 0x101062c + field public static final int windowSplashScreenBehavior; field public static final int windowSplashScreenBrandingImage = 16844335; // 0x101062f field public static final int windowSplashScreenIconBackgroundColor = 16844336; // 0x1010630 field @Deprecated public static final int windowSplashscreenContent = 16844132; // 0x1010564 @@ -3061,6 +3065,7 @@ package android.accessibilityservice { method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController(); method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController(int); method @NonNull @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController(); + method @Nullable public final android.accessibilityservice.InputMethod getInputMethod(); method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController(); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); @@ -3073,6 +3078,7 @@ package android.accessibilityservice { method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public final android.os.IBinder onBind(android.content.Intent); + method @NonNull public android.accessibilityservice.InputMethod onCreateInputMethod(); method @Deprecated protected boolean onGesture(int); method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent); method public abstract void onInterrupt(); @@ -3260,6 +3266,7 @@ package android.accessibilityservice { field public static final int FEEDBACK_VISUAL = 8; // 0x8 field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80 field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2 + field public static final int FLAG_INPUT_METHOD_EDITOR = 32768; // 0x8000 field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10 field public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 8192; // 0x2000 field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100 @@ -3320,6 +3327,28 @@ package android.accessibilityservice { method public boolean willContinue(); } + public class InputMethod { + ctor protected InputMethod(@NonNull android.accessibilityservice.AccessibilityService); + method @Nullable public final android.accessibilityservice.InputMethod.AccessibilityInputConnection getCurrentInputConnection(); + method @Nullable public final android.view.inputmethod.EditorInfo getCurrentInputEditorInfo(); + method public final boolean getCurrentInputStarted(); + method public void onFinishInput(); + method public void onStartInput(@NonNull android.view.inputmethod.EditorInfo, boolean); + method public void onUpdateSelection(int, int, int, int, int, int); + } + + public final class InputMethod.AccessibilityInputConnection { + method public void clearMetaKeyStates(int); + method public void commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute); + method public void deleteSurroundingText(int, int); + method public int getCursorCapsMode(int); + method @Nullable public android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int); + method public void performContextMenuAction(int); + method public void performEditorAction(int); + method public void sendKeyEvent(@NonNull android.view.KeyEvent); + method public void setSelection(int, int); + } + public final class MagnificationConfig implements android.os.Parcelable { method public int describeContents(); method public float getCenterX(); @@ -4243,6 +4272,7 @@ package android.app { method @Deprecated public final void setProgressBarIndeterminate(boolean); method @Deprecated public final void setProgressBarIndeterminateVisibility(boolean); method @Deprecated public final void setProgressBarVisibility(boolean); + method public void setRecentsScreenshotEnabled(boolean); method public void setRequestedOrientation(int); method public final void setResult(int); method public final void setResult(int, android.content.Intent); @@ -4505,6 +4535,7 @@ package android.app { method @Nullable public android.graphics.Rect getLaunchBounds(); method public int getLaunchDisplayId(); method public boolean getLockTaskMode(); + method public int getSplashScreenStyle(); method public boolean isPendingIntentBackgroundActivityLaunchAllowed(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); @@ -7334,6 +7365,7 @@ package android.app.admin { method public int getCurrentFailedPasswordAttempts(); method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); + method @Nullable public String getDeviceManagerRoleHolderPackageName(); method public CharSequence getDeviceOwnerLockScreenInfo(); method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); @@ -7629,6 +7661,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE"; field public static final String EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT"; field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER"; + field public static final String EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT = "android.app.extra.PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT"; field public static final String EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS = "android.app.extra.PROVISIONING_SKIP_EDUCATION_SCREENS"; field public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION"; field @Deprecated public static final String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT"; @@ -7651,6 +7684,7 @@ package android.app.admin { field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID"; field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE"; field public static final String EXTRA_RESOURCE_TYPE_STRING = "android.app.extra.RESOURCE_TYPE_STRING"; + field public static final String EXTRA_RESULT_LAUNCH_INTENT = "android.app.extra.RESULT_LAUNCH_INTENT"; field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1 field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2 field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1 @@ -26438,7 +26472,7 @@ package android.net { method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]); method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate); method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean); - method @NonNull public android.net.Ikev2VpnProfile.Builder setExcludeLocalRoutes(boolean); + method @NonNull public android.net.Ikev2VpnProfile.Builder setLocalRoutesExcluded(boolean); method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int); method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean); method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo); @@ -26516,7 +26550,7 @@ package android.net { } public abstract class PlatformVpnProfile { - method public final boolean getExcludeLocalRoutes(); + method public final boolean areLocalRoutesExcluded(); method public final boolean getRequiresInternetValidation(); method public final int getType(); method @NonNull public final String getTypeString(); @@ -26713,6 +26747,23 @@ package android.net { method @Deprecated public void startProvisionedVpnProfile(); method @NonNull public String startProvisionedVpnProfileSession(); method public void stopProvisionedVpnProfile(); + field public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT"; + field public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER = "android.net.category.EVENT_DEACTIVATED_BY_USER"; + field public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR"; + field public static final String CATEGORY_EVENT_NETWORK_ERROR = "android.net.category.EVENT_NETWORK_ERROR"; + field public static final int ERROR_CLASS_NOT_RECOVERABLE = 1; // 0x1 + field public static final int ERROR_CLASS_RECOVERABLE = 2; // 0x2 + field public static final int ERROR_CODE_NETWORK_IO = 3; // 0x3 + field public static final int ERROR_CODE_NETWORK_LOST = 2; // 0x2 + field public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1; // 0x1 + field public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; // 0x0 + field public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS"; + field public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE"; + field public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY"; + field public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP"; + field public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES"; + field public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK"; + field public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES"; } public class VpnService extends android.app.Service { @@ -32003,6 +32054,7 @@ package android.os { method public boolean isDemoUser(); method public static boolean isHeadlessSystemUserMode(); method public boolean isManagedProfile(); + method public boolean isProfile(); method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); method public boolean isUserAGoat(); @@ -38838,6 +38890,7 @@ package android.service.notification { field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 + field public static final int REASON_ASSISTANT_CANCEL = 22; // 0x16 field public static final int REASON_CANCEL = 2; // 0x2 field public static final int REASON_CANCEL_ALL = 3; // 0x3 field public static final int REASON_CHANNEL_BANNED = 17; // 0x11 @@ -41305,13 +41358,13 @@ package android.telephony { field public static final int IPSEC_ENCRYPTION_ALGORITHM_AES_CBC = 2; // 0x2 field public static final int IPSEC_ENCRYPTION_ALGORITHM_DES_EDE3_CBC = 1; // 0x1 field public static final int IPSEC_ENCRYPTION_ALGORITHM_NULL = 0; // 0x0 - field public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = "ims.key_capability_type_call_composer_int_array"; - field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.key_capability_type_options_uce_int_array"; - field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.key_capability_type_presence_uce_int_array"; - field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.key_capability_type_sms_int_array"; - field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.key_capability_type_ut_int_array"; - field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.key_capability_type_video_int_array"; - field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.key_capability_type_voice_int_array"; + field public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY = "ims.capability_type_call_composer_int_array"; + field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.capability_type_options_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.capability_type_presence_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.capability_type_sms_int_array"; + field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.capability_type_ut_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.capability_type_video_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.capability_type_voice_int_array"; field public static final String KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL = "ims.enable_presence_capability_exchange_bool"; field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool"; field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool"; @@ -43283,6 +43336,7 @@ package android.telephony { method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); method @Deprecated public void listen(android.telephony.PhoneStateListener, int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void rebootModem(); method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback); method public void registerTelephonyCallback(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); @@ -51984,8 +52038,8 @@ package android.view.accessibility { method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle(); method public boolean isCallCaptioningEnabled(); method public final boolean isEnabled(); - method public final boolean isSystemAudioCaptioningRequested(); - method public final boolean isSystemAudioCaptioningUiRequested(); + method public final boolean isSystemAudioCaptioningEnabled(); + method public final boolean isSystemAudioCaptioningUiEnabled(); method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 5df593d59f83..dc631278653d 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -250,6 +250,22 @@ package android.media.session { package android.net { + public class EthernetManager { + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void addInterfaceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.InterfaceStateListener); + method public void removeInterfaceStateListener(@NonNull android.net.EthernetManager.InterfaceStateListener); + method public void setIncludeTestInterfaces(boolean); + field public static final int ROLE_CLIENT = 1; // 0x1 + field public static final int ROLE_NONE = 0; // 0x0 + field public static final int ROLE_SERVER = 2; // 0x2 + field public static final int STATE_ABSENT = 0; // 0x0 + field public static final int STATE_LINK_DOWN = 1; // 0x1 + field public static final int STATE_LINK_UP = 2; // 0x2 + } + + public static interface EthernetManager.InterfaceStateListener { + method public void onInterfaceStateChanged(@NonNull String, int, int, @Nullable android.net.IpConfiguration); + } + public class LocalSocket implements java.io.Closeable { ctor public LocalSocket(@NonNull java.io.FileDescriptor); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d7517b24d04b..6da8933f0619 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -195,7 +195,6 @@ package android { field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION"; field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN"; - field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN"; field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE"; field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED"; field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"; @@ -227,7 +226,6 @@ package android { field public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY"; field public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD = "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"; - field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD"; field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS"; @@ -1147,11 +1145,16 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME"; field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE"; field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT"; + field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE"; + field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER"; + field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION"; + field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM"; field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER"; field public static final String EXTRA_PROVISIONING_SUPPORTED_MODES = "android.app.extra.PROVISIONING_SUPPORTED_MODES"; field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL"; field public static final String EXTRA_PROVISIONING_TRIGGER = "android.app.extra.PROVISIONING_TRIGGER"; field public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; + field public static final String EXTRA_ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE = "android.app.extra.ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE"; field public static final String EXTRA_ROLE_HOLDER_STATE = "android.app.extra.ROLE_HOLDER_STATE"; field public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 4; // 0x4 field public static final int FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED = 1; // 0x1 @@ -1359,14 +1362,13 @@ package android.app.ambientcontext { method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle); } - public final class AmbientContextEventResponse implements android.os.Parcelable { - method public int describeContents(); - method @Nullable public android.app.PendingIntent getActionPendingIntent(); - method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents(); - method @NonNull public String getPackageName(); - method public int getStatusCode(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventResponse> CREATOR; + public final class AmbientContextManager { + method @NonNull public static java.util.List<android.app.ambientcontext.AmbientContextEvent> getEventsFromIntent(@NonNull android.content.Intent); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void queryAmbientContextServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void startConsentActivity(@NonNull java.util.Set<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver(); + field public static final String EXTRA_AMBIENT_CONTEXT_EVENTS = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS"; field public static final int STATUS_ACCESS_DENIED = 5; // 0x5 field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4 field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2 @@ -1375,27 +1377,14 @@ package android.app.ambientcontext { field public static final int STATUS_UNKNOWN = 0; // 0x0 } - public static final class AmbientContextEventResponse.Builder { - ctor public AmbientContextEventResponse.Builder(); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build(); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String); - method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int); - } - - public final class AmbientContextManager { - method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent); - method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent); - method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver(); - field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; - } - } package android.app.assist { - public class ActivityId { + public final class ActivityId implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.ActivityId> CREATOR; } public static class AssistStructure.ViewNode { @@ -2761,6 +2750,7 @@ package android.companion.virtual { method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener, @NonNull java.util.concurrent.Executor); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback); method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); @@ -2778,8 +2768,8 @@ package android.companion.virtual { method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR; - field public static final int LOCK_STATE_ALWAYS_LOCKED = 0; // 0x0 field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1 + field public static final int LOCK_STATE_DEFAULT = 0; // 0x0 } public static final class VirtualDeviceParams.Builder { @@ -2793,6 +2783,39 @@ package android.companion.virtual { } +package android.companion.virtual.audio { + + public final class AudioCapture { + ctor public AudioCapture(); + method public int getRecordingState(); + method public int read(@NonNull java.nio.ByteBuffer, int); + method public void startRecording(); + method public void stop(); + } + + public final class AudioInjection { + ctor public AudioInjection(); + method public int getPlayState(); + method public void play(); + method public void stop(); + method public int write(@NonNull java.nio.ByteBuffer, int, int); + } + + public final class VirtualAudioDevice implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void close(); + method @Nullable public android.companion.virtual.audio.AudioCapture getAudioCapture(); + method @Nullable public android.companion.virtual.audio.AudioInjection getAudioInjection(); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.companion.virtual.audio.AudioCapture startAudioCapture(@NonNull android.media.AudioFormat); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.companion.virtual.audio.AudioInjection startAudioInjection(@NonNull android.media.AudioFormat); + } + + public static interface VirtualAudioDevice.AudioConfigurationChangeCallback { + method public void onPlaybackConfigChanged(@NonNull java.util.List<android.media.AudioPlaybackConfiguration>); + method public void onRecordingConfigChanged(@NonNull java.util.List<android.media.AudioRecordingConfiguration>); + } + +} + package android.content { public class ApexEnvironment { @@ -5997,6 +6020,7 @@ package android.media { public class AudioManager { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull java.util.List<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException; @@ -6004,7 +6028,9 @@ package android.media { method public void clearAudioServerStateCallback(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<java.lang.Integer> getActiveAssistantServicesUids(); method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<java.lang.Integer> getAssistantServicesUids(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat); @@ -6029,6 +6055,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback); method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull java.util.List<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener); @@ -6036,6 +6063,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setActiveAssistantServiceUids(@NonNull java.util.List<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long); method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); @@ -6171,6 +6199,10 @@ package android.media { method @NonNull public android.media.HwAudioSource.Builder setAudioDeviceInfo(@NonNull android.media.AudioDeviceInfo); } + public final class MediaCodec { + method @NonNull @RequiresPermission("android.permission.MEDIA_RESOURCE_OVERRIDE_PID") public static android.media.MediaCodec createByCodecNameForClient(@NonNull String, int, int) throws java.io.IOException; + } + public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation { method @RequiresPermission(android.Manifest.permission.BIND_IMS_SERVICE) public void setOnRtpRxNoticeListener(@NonNull android.content.Context, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaPlayer.OnRtpRxNoticeListener); } @@ -6667,7 +6699,7 @@ package android.media.tv { method @NonNull @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String); - method public int getClientPriority(int, @Nullable String); + method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @Nullable String); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos(); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String); @@ -9659,7 +9691,6 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isMediaSharedWithParent(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser(); - method public boolean isProfile(); method public boolean isRestrictedProfile(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public boolean isRestrictedProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle); @@ -10701,14 +10732,45 @@ package android.security.keystore.recovery { package android.service.ambientcontext { + public final class AmbientContextDetectionResult implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents(); + method @NonNull public String getPackageName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.ambientcontext.AmbientContextDetectionResult> CREATOR; + } + + public static final class AmbientContextDetectionResult.Builder { + ctor public AmbientContextDetectionResult.Builder(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult build(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder setPackageName(@NonNull String); + } + public abstract class AmbientContextDetectionService extends android.app.Service { ctor public AmbientContextDetectionService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); - method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.app.ambientcontext.AmbientContextEventResponse>); + method @BinderThread public abstract void onQueryServiceStatus(@NonNull int[], @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>); + method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>); method public abstract void onStopDetection(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; } + public final class AmbientContextDetectionServiceStatus implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getPackageName(); + method public int getStatusCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.ambientcontext.AmbientContextDetectionServiceStatus> CREATOR; + } + + public static final class AmbientContextDetectionServiceStatus.Builder { + ctor public AmbientContextDetectionServiceStatus.Builder(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus build(); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setPackageName(@NonNull String); + method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setStatusCode(int); + } + } package android.service.appprediction { @@ -13256,7 +13318,7 @@ package android.telephony { method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot(); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); @@ -13812,7 +13874,7 @@ package android.telephony.euicc { field public static final int RESULT_CALLER_NOT_ALLOWED = -3; // 0xfffffffd field public static final int RESULT_EUICC_NOT_FOUND = -2; // 0xfffffffe field public static final int RESULT_OK = 0; // 0x0 - field public static final int RESULT_PROFILE_NOT_FOUND = -4; // 0xfffffffc + field public static final int RESULT_PROFILE_NOT_FOUND = 1; // 0x1 field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff } @@ -15531,8 +15593,8 @@ package android.view.accessibility { } public class CaptioningManager { - method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningRequested(boolean); - method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiRequested(boolean); + method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningEnabled(boolean); + method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiEnabled(boolean); } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 5bd6ca827e29..df09242fbffd 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -278,6 +278,7 @@ package android.app { } public final class GameManager { + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public int getGameMode(@NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public boolean isAngleEnabled(@NonNull String); method public void setGameServiceProvider(@Nullable String); } @@ -569,9 +570,13 @@ package android.app.admin { package android.app.assist { - public class ActivityId { + public final class ActivityId implements android.os.Parcelable { + ctor public ActivityId(int, @Nullable android.os.IBinder); + method public int describeContents(); method public int getTaskId(); method @Nullable public android.os.IBinder getToken(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.ActivityId> CREATOR; } } @@ -1602,10 +1607,6 @@ package android.media.tv.tuner { package android.net { - public class EthernetManager { - method public void setIncludeTestInterfaces(boolean); - } - public class NetworkPolicyManager { method public boolean getRestrictBackground(); method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean); @@ -1748,6 +1749,7 @@ package android.os { public class Process { method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException; + method public static final int toSupplementalUid(int); field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90 field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8 field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7 @@ -2862,6 +2864,7 @@ package android.view { method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); method public default boolean shouldShowSystemDecors(int); + method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int); field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1 field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2 field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 50473f1d8c84..cf3ca20315d8 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -40,6 +40,8 @@ import android.graphics.ParcelableColorSpace; import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; +import android.inputmethodservice.IInputMethodSessionWrapper; +import android.inputmethodservice.RemoteInputConnection; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -65,14 +67,23 @@ import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityWindowInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodSession; +import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.IInputSessionWithIdCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -627,6 +638,21 @@ public abstract class AccessibilityService extends Service { void onAccessibilityButtonAvailabilityChanged(boolean available); /** This is called when the system action list is changed. */ void onSystemActionsChanged(); + /** This is called when an app requests ime sessions or when the service is enabled. */ + void createImeSession(IInputSessionWithIdCallback callback); + /** + * This is called when InputMethodManagerService requests to set the session enabled or + * disabled + */ + void setImeSessionEnabled(InputMethodSession session, boolean enabled); + /** This is called when an app binds input or when the service is enabled. */ + void bindInput(InputBinding binding); + /** This is called when an app unbinds input or when the service is disabled. */ + void unbindInput(); + /** This is called when an app starts input or when the service is enabled. */ + void startInput(@Nullable InputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting, + @NonNull IBinder startInputToken); } /** @@ -763,6 +789,8 @@ public abstract class AccessibilityService extends Service { new SparseArray<>(0); private SoftKeyboardController mSoftKeyboardController; + private InputMethod mInputMethod; + private boolean mInputMethodInitialized = false; private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers = new SparseArray<>(0); @@ -797,6 +825,17 @@ public abstract class AccessibilityService extends Service { for (int i = 0; i < mMagnificationControllers.size(); i++) { mMagnificationControllers.valueAt(i).onServiceConnectedLocked(); } + AccessibilityServiceInfo info = getServiceInfo(); + if (info != null) { + boolean requestIme = (info.flags + & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0; + if (requestIme && !mInputMethodInitialized) { + mInputMethod = onCreateInputMethod(); + mInputMethodInitialized = true; + } + } else { + Log.e(LOG_TAG, "AccessibilityServiceInfo is null in dispatchServiceConnected"); + } } if (mSoftKeyboardController != null) { mSoftKeyboardController.onServiceConnected(); @@ -1849,6 +1888,32 @@ public abstract class AccessibilityService extends Service { } } + /** + * The default implementation returns our default {@link InputMethod}. Subclasses can override + * it to provide their own customized version. Accessibility services need to set the + * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs. + * + * @return the InputMethod. + */ + @NonNull + public InputMethod onCreateInputMethod() { + return new InputMethod(this); + } + + /** + * Returns the InputMethod instance after the system calls {@link #onCreateInputMethod()}, + * which may be used to input text or get editable text selection change notifications. It will + * return null if the accessibility service doesn't set the + * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag or the system doesn't call + * {@link #onCreateInputMethod()}. + * + * @return the InputMethod instance + */ + @Nullable + public final InputMethod getInputMethod() { + return mInputMethod; + } + private void onSoftKeyboardShowModeChanged(int showMode) { if (mSoftKeyboardController != null) { mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode); @@ -2657,6 +2722,47 @@ public abstract class AccessibilityService extends Service { public void onSystemActionsChanged() { AccessibilityService.this.onSystemActionsChanged(); } + + @Override + public void createImeSession(IInputSessionWithIdCallback callback) { + if (mInputMethod != null) { + mInputMethod.createImeSession(callback); + } + } + + @Override + public void setImeSessionEnabled(InputMethodSession session, boolean enabled) { + if (mInputMethod != null) { + mInputMethod.setImeSessionEnabled(session, enabled); + } + } + + @Override + public void bindInput(InputBinding binding) { + if (mInputMethod != null) { + mInputMethod.bindInput(binding); + } + } + + @Override + public void unbindInput() { + if (mInputMethod != null) { + mInputMethod.unbindInput(); + } + } + + @Override + public void startInput(@Nullable InputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting, + @NonNull IBinder startInputToken) { + if (mInputMethod != null) { + if (restarting) { + mInputMethod.restartInput(inputConnection, editorInfo); + } else { + mInputMethod.startInput(inputConnection, editorInfo); + } + } + } }); } @@ -2682,6 +2788,11 @@ public abstract class AccessibilityService extends Service { private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12; private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13; private static final int DO_ON_SYSTEM_ACTIONS_CHANGED = 14; + private static final int DO_CREATE_IME_SESSION = 15; + private static final int DO_SET_IME_SESSION_ENABLED = 16; + private static final int DO_BIND_INPUT = 17; + private static final int DO_UNBIND_INPUT = 18; + private static final int DO_START_INPUT = 19; private final HandlerCaller mCaller; @@ -2690,6 +2801,22 @@ public abstract class AccessibilityService extends Service { private int mConnectionId = AccessibilityInteractionClient.NO_ID; + /** + * This is not {@null} only between {@link #bindInput(InputBinding)} and + * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if + * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary + * blocking operations. + * + * <p>This field must be set and cleared only from the binder thread(s), where the system + * guarantees that {@link #bindInput(InputBinding)}, + * {@link #startInput(IBinder, IInputContext, EditorInfo, boolean)}, and + * {@link #unbindInput()} are called with the same order as the original calls + * in {@link com.android.server.inputmethod.InputMethodManagerService}. + * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p> + */ + @Nullable + CancellationGroup mCancellationGroup = null; + public IAccessibilityServiceClientWrapper(Context context, Looper looper, Callbacks callback) { mCallback = callback; @@ -2783,6 +2910,70 @@ public abstract class AccessibilityService extends Service { mCaller.sendMessage(mCaller.obtainMessage(DO_ON_SYSTEM_ACTIONS_CHANGED)); } + /** This is called when an app requests ime sessions or when the service is enabled. */ + public void createImeSession(IInputSessionWithIdCallback callback) { + final Message message = mCaller.obtainMessageO(DO_CREATE_IME_SESSION, callback); + mCaller.sendMessage(message); + } + + /** + * This is called when InputMethodManagerService requests to set the session enabled or + * disabled + */ + public void setImeSessionEnabled(IInputMethodSession session, boolean enabled) { + try { + InputMethodSession ls = ((IInputMethodSessionWrapper) + session).getInternalInputMethodSession(); + if (ls == null) { + Log.w(LOG_TAG, "Session is already finished: " + session); + return; + } + mCaller.sendMessage(mCaller.obtainMessageIO( + DO_SET_IME_SESSION_ENABLED, enabled ? 1 : 0, ls)); + } catch (ClassCastException e) { + Log.w(LOG_TAG, "Incoming session not of correct type: " + session, e); + } + } + + /** This is called when an app binds input or when the service is enabled. */ + public void bindInput(InputBinding binding) { + if (mCancellationGroup != null) { + Log.e(LOG_TAG, "bindInput must be paired with unbindInput."); + } + mCancellationGroup = new CancellationGroup(); + InputConnection ic = new RemoteInputConnection(new WeakReference<>(() -> mContext), + IInputContext.Stub.asInterface(binding.getConnectionToken()), + mCancellationGroup); + InputBinding nu = new InputBinding(ic, binding); + final Message message = mCaller.obtainMessageO(DO_BIND_INPUT, nu); + mCaller.sendMessage(message); + } + + /** This is called when an app unbinds input or when the service is disabled. */ + public void unbindInput() { + if (mCancellationGroup != null) { + // Signal the flag then forget it. + mCancellationGroup.cancelAll(); + mCancellationGroup = null; + } else { + Log.e(LOG_TAG, "unbindInput must be paired with bindInput."); + } + mCaller.sendMessage(mCaller.obtainMessage(DO_UNBIND_INPUT)); + } + + /** This is called when an app starts input or when the service is enabled. */ + public void startInput(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + if (mCancellationGroup == null) { + Log.e(LOG_TAG, "startInput must be called after bindInput."); + mCancellationGroup = new CancellationGroup(); + } + final Message message = mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, + inputContext, editorInfo, mCancellationGroup, restarting ? 1 : 0, + 0 /* unused */); + mCaller.sendMessage(message); + } + @Override public void onMotionEvent(MotionEvent event) { final Message message = PooledLambda.obtainMessage( @@ -2948,6 +3139,50 @@ public abstract class AccessibilityService extends Service { } return; } + case DO_CREATE_IME_SESSION: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + IInputSessionWithIdCallback callback = + (IInputSessionWithIdCallback) message.obj; + mCallback.createImeSession(callback); + } + return; + } + case DO_SET_IME_SESSION_ENABLED: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.setImeSessionEnabled((InputMethodSession) message.obj, + message.arg1 != 0); + } + return; + } + case DO_BIND_INPUT: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.bindInput((InputBinding) message.obj); + } + return; + } + case DO_UNBIND_INPUT: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + mCallback.unbindInput(); + } + return; + } + case DO_START_INPUT: { + if (mConnectionId != AccessibilityInteractionClient.NO_ID) { + final SomeArgs args = (SomeArgs) message.obj; + final IBinder startInputToken = (IBinder) args.arg1; + final IInputContext inputContext = (IInputContext) args.arg2; + final EditorInfo info = (EditorInfo) args.arg3; + final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; + final boolean restarting = args.argi5 == 1; + final InputConnection ic = inputContext != null + ? new RemoteInputConnection(new WeakReference<>(() -> mContext), + inputContext, cancellationGroup) : null; + info.makeCompatible(mContext.getApplicationInfo().targetSdkVersion); + mCallback.startInput(ic, info, restarting, startInputToken); + args.recycle(); + } + return; + } default: Log.w(LOG_TAG, "Unknown message type " + message.what); } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 6988048c34e1..85e78540332f 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -390,6 +390,15 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int FLAG_SEND_MOTION_EVENTS = 0x0004000; + /** + * This flag makes the AccessibilityService an input method editor with a subset of input + * method editor capabilities: get the {@link android.view.inputmethod.InputConnection} and get + * text selection change notifications. + * + * @see AccessibilityService#getInputMethod() + */ + public static final int FLAG_INPUT_METHOD_EDITOR = 0x0008000; + /** {@hide} */ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000; @@ -497,6 +506,7 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK + * @see #FLAG_INPUT_METHOD_EDITOR */ public int flags; @@ -1356,6 +1366,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "FLAG_REQUEST_FINGERPRINT_GESTURES"; case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK: return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK"; + case FLAG_INPUT_METHOD_EDITOR: + return "FLAG_INPUT_METHOD_EDITOR"; default: return null; } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl index 375383d5d858..94da61f82d29 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -24,6 +24,11 @@ import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.MagnificationConfig; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.IInputSessionWithIdCallback; /** * Top-level interface to an accessibility service component. @@ -63,4 +68,15 @@ import android.view.MotionEvent; void onAccessibilityButtonAvailabilityChanged(boolean available); void onSystemActionsChanged(); + + void createImeSession(IInputSessionWithIdCallback callback); + + void setImeSessionEnabled(IInputMethodSession session, boolean enabled); + + void bindInput(in InputBinding binding); + + void unbindInput(); + + void startInput(in IBinder startInputToken, in IInputContext inputContext, + in EditorInfo editorInfo, boolean restarting); } diff --git a/core/java/android/accessibilityservice/InputMethod.java b/core/java/android/accessibilityservice/InputMethod.java new file mode 100644 index 000000000000..001d804b22d6 --- /dev/null +++ b/core/java/android/accessibilityservice/InputMethod.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2022 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 android.accessibilityservice; + +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; + +import android.annotation.CallbackExecutor; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.graphics.Rect; +import android.inputmethodservice.IInputMethodSessionWrapper; +import android.inputmethodservice.RemoteInputConnection; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.Trace; +import android.util.Log; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSession; +import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; + +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputSessionWithIdCallback; + +import java.util.concurrent.Executor; + +/** + * This class provides input method APIs. Some public methods such as + * @link #onUpdateSelection(int, int, int, int, int, int)} do nothing by default and service + * developers should override them as needed. Developers should also override + * {@link AccessibilityService#onCreateInputMethod()} to return + * their custom InputMethod implementation. Accessibility services also need to set the + * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs. + */ +public class InputMethod { + private static final String LOG_TAG = "A11yInputMethod"; + + private final AccessibilityService mService; + private InputBinding mInputBinding; + private InputConnection mInputConnection; + private boolean mInputStarted; + private InputConnection mStartedInputConnection; + private EditorInfo mInputEditorInfo; + + protected InputMethod(@NonNull AccessibilityService service) { + mService = service; + } + + /** + * Retrieve the currently active InputConnection that is bound to + * the input method, or null if there is none. + */ + @Nullable + public final AccessibilityInputConnection getCurrentInputConnection() { + InputConnection ic = mStartedInputConnection; + if (ic != null) { + return new AccessibilityInputConnection(ic); + } + if (mInputConnection != null) { + return new AccessibilityInputConnection(mInputConnection); + } + return null; + } + + /** + * Whether the input has started. + */ + public final boolean getCurrentInputStarted() { + return mInputStarted; + } + + /** + * Get the EditorInfo which describes several attributes of a text editing object + * that an accessibility service is communicating with (typically an EditText). + */ + @Nullable + public final EditorInfo getCurrentInputEditorInfo() { + return mInputEditorInfo; + } + + /** + * Called to inform the accessibility service that text input has started in an + * editor. You should use this callback to initialize the state of your + * input to match the state of the editor given to it. + * + * @param attribute The attributes of the editor that input is starting + * in. + * @param restarting Set to true if input is restarting in the same + * editor such as because the application has changed the text in + * the editor. Otherwise will be false, indicating this is a new + * session with the editor. + */ + public void onStartInput(@NonNull EditorInfo attribute, boolean restarting) { + // Intentionally empty + } + + /** + * Called to inform the accessibility service that text input has finished in + * the last editor. At this point there may be a call to + * {@link #onStartInput(EditorInfo, boolean)} to perform input in a + * new editor, or the accessibility service may be left idle. This method is + * <em>not</em> called when input restarts in the same editor. + * + * <p>The default + * implementation uses the InputConnection to clear any active composing + * text; you can override this (not calling the base class implementation) + * to perform whatever behavior you would like. + */ + public void onFinishInput() { + InputConnection ic = mStartedInputConnection != null ? mStartedInputConnection + : mInputConnection; + if (ic != null) { + ic.finishComposingText(); + } + } + + /** + * Called when the application has reported a new selection region of + * the text. This is called whether or not the accessibility service has requested + * extracted text updates, although if so it will not receive this call + * if the extracted text has changed as well. + * + * <p>Be careful about changing the text in reaction to this call with + * methods such as setComposingText, commitText or + * deleteSurroundingText. If the cursor moves as a result, this method + * will be called again, which may result in an infinite loop. + */ + public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, + int newSelEnd, int candidatesStart, int candidatesEnd) { + // Intentionally empty + } + + final void createImeSession(IInputSessionWithIdCallback callback) { + InputMethodSession session = onCreateInputMethodSessionInterface(); + try { + IInputMethodSessionWrapper wrap = + new IInputMethodSessionWrapper(mService, session, null); + callback.sessionCreated(wrap, mService.getConnectionId()); + } catch (RemoteException ignored) { + } + } + + final void setImeSessionEnabled(@NonNull InputMethodSession session, boolean enabled) { + ((InputMethodSessionForAccessibility) session).setEnabled(enabled); + } + + final void bindInput(@NonNull InputBinding binding) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.bindInput"); + mInputBinding = binding; + mInputConnection = binding.getConnection(); + Log.v(LOG_TAG, "bindInput(): binding=" + binding + + " ic=" + mInputConnection); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + + final void unbindInput() { + Log.v(LOG_TAG, "unbindInput(): binding=" + mInputBinding + + " ic=" + mInputConnection); + // Unbind input is per process per display. + mInputBinding = null; + mInputConnection = null; + } + + final void startInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) { + Log.v(LOG_TAG, "startInput(): editor=" + attribute); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput"); + doStartInput(ic, attribute, false /* restarting */); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + + final void restartInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) { + Log.v(LOG_TAG, "restartInput(): editor=" + attribute); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.restartInput"); + doStartInput(ic, attribute, true /* restarting */); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + + + final void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { + if (!restarting && mInputStarted) { + doFinishInput(); + } + mInputStarted = true; + mStartedInputConnection = ic; + mInputEditorInfo = attribute; + Log.v(LOG_TAG, "CALL: onStartInput"); + onStartInput(attribute, restarting); + } + + final void doFinishInput() { + Log.v(LOG_TAG, "CALL: doFinishInput"); + if (mInputStarted) { + Log.v(LOG_TAG, "CALL: onFinishInput"); + onFinishInput(); + } + mInputStarted = false; + mStartedInputConnection = null; + } + + private InputMethodSession onCreateInputMethodSessionInterface() { + return new InputMethodSessionForAccessibility(); + } + + /** + * This class provides the allowed list of {@link InputConnection} APIs for + * accessibility services. + */ + public final class AccessibilityInputConnection { + private InputConnection mIc; + AccessibilityInputConnection(InputConnection ic) { + this.mIc = ic; + } + + /** + * Commit text to the text box and set the new cursor position. This method is + * used to allow the IME to provide extra information while setting up text. + * + * <p>This method commits the contents of the currently composing text, and then + * moves the cursor according to {@code newCursorPosition}. If there + * is no composing text when this method is called, the new text is + * inserted at the cursor position, removing text inside the selection + * if any. + * + * <p>Calling this method will cause the editor to call + * {@link #onUpdateSelection(int, int, int, int, + * int, int)} on the current accessibility service after the batch input is over. + * <strong>Editor authors</strong>, for this to happen you need to + * make the changes known to the accessibility service by calling + * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, + * but be careful to wait until the batch edit is over if one is + * in progress.</p> + * + * @param text The text to commit. This may include styles. + * @param newCursorPosition The new cursor position around the text, + * in Java characters. If > 0, this is relative to the end + * of the text - 1; if <= 0, this is relative to the start + * of the text. So a value of 1 will always advance the cursor + * to the position after the full text being inserted. Note that + * this means you can't position the cursor within the text, + * because the editor can make modifications to the text + * you are providing so it is not possible to correctly specify + * locations there. + * @param textAttribute The extra information about the text. + */ + public void commitText(@NonNull CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + if (mIc != null) { + mIc.commitText(text, newCursorPosition, textAttribute); + } + } + + /** + * Set the selection of the text editor. To set the cursor + * position, start and end should have the same value. + * + * <p>Since this moves the cursor, calling this method will cause + * the editor to call + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, + * int,int, int)} on the current IME after the batch input is over. + * <strong>Editor authors</strong>, for this to happen you need to + * make the changes known to the input method by calling + * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, + * but be careful to wait until the batch edit is over if one is + * in progress.</p> + * + * <p>This has no effect on the composing region which must stay + * unchanged. The order of start and end is not important. In + * effect, the region from start to end and the region from end to + * start is the same. Editor authors, be ready to accept a start + * that is greater than end.</p> + * + * @param start the character index where the selection should start. + * @param end the character index where the selection should end. + */ + public void setSelection(int start, int end) { + if (mIc != null) { + mIc.setSelection(start, end); + } + } + + /** + * Gets the surrounding text around the current cursor, with <var>beforeLength</var> + * characters of text before the cursor (start of the selection), <var>afterLength</var> + * characters of text after the cursor (end of the selection), and all of the selected + * text. The range are for java characters, not glyphs that can be multiple characters. + * + * <p>This method may fail either if the input connection has become invalid (such as its + * process crashing), or the client is taking too long to respond with the text (it is + * given a couple seconds to return), or the protocol is not supported. In any of these + * cases, null is returned. + * + * <p>This method does not affect the text in the editor in any way, nor does it affect the + * selection or composing spans.</p> + * + * <p>If {@link InputConnection#GET_TEXT_WITH_STYLES} is supplied as flags, the editor + * should return a {@link android.text.Spanned} with all the spans set on the text.</p> + * + * <p><strong>Accessibility service authors:</strong> please consider this will trigger an + * IPC round-trip that will take some time. Assume this method consumes a lot of time. + * + * @param beforeLength The expected length of the text before the cursor. + * @param afterLength The expected length of the text after the cursor. + * @param flags Supplies additional options controlling how the text is returned. May be + * either {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}. + * @return an {@link android.view.inputmethod.SurroundingText} object describing the + * surrounding text and state of selection, or null if the input connection is no longer + * valid, or the editor can't comply with the request for some reason, or the application + * does not implement this method. The length of the returned text might be less than the + * sum of <var>beforeLength</var> and <var>afterLength</var> . + * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is + * negative. + */ + @Nullable + public SurroundingText getSurroundingText( + @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, + @InputConnection.GetTextType int flags) { + if (mIc != null) { + return mIc.getSurroundingText(beforeLength, afterLength, flags); + } + return null; + } + + /** + * Delete <var>beforeLength</var> characters of text before the + * current cursor position, and delete <var>afterLength</var> + * characters of text after the current cursor position, excluding + * the selection. Before and after refer to the order of the + * characters in the string, not to their visual representation: + * this means you don't have to figure out the direction of the + * text and can just use the indices as-is. + * + * <p>The lengths are supplied in Java chars, not in code points + * or in glyphs.</p> + * + * <p>Since this method only operates on text before and after the + * selection, it can't affect the contents of the selection. This + * may affect the composing span if the span includes characters + * that are to be deleted, but otherwise will not change it. If + * some characters in the composing span are deleted, the + * composing span will persist but get shortened by however many + * chars inside it have been removed.</p> + * + * <p><strong>Accessibility service authors:</strong> please be careful not to + * delete only half of a surrogate pair. Also take care not to + * delete more characters than are in the editor, as that may have + * ill effects on the application. Calling this method will cause + * the editor to call + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on your service after the batch input is over.</p> + * + * <p><strong>Editor authors:</strong> please be careful of race + * conditions in implementing this call. An IME can make a change + * to the text or change the selection position and use this + * method right away; you need to make sure the effects are + * consistent with the results of the latest edits. Also, although + * the IME should not send lengths bigger than the contents of the + * string, you should check the values for overflows and trim the + * indices to the size of the contents to avoid crashes. Since + * this changes the contents of the editor, you need to make the + * changes known to the input method by calling + * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, + * but be careful to wait until the batch edit is over if one is + * in progress.</p> + * + * @param beforeLength The number of characters before the cursor to be deleted, in code + * unit. If this is greater than the number of existing characters between the + * beginning of the text and the cursor, then this method does not fail but deletes + * all the characters in that range. + * @param afterLength The number of characters after the cursor to be deleted, in code unit. + * If this is greater than the number of existing characters between the cursor and + * the end of the text, then this method does not fail but deletes all the characters + * in that range. + */ + public void deleteSurroundingText(int beforeLength, int afterLength) { + if (mIc != null) { + mIc.deleteSurroundingText(beforeLength, afterLength); + } + } + + /** + * Send a key event to the process that is currently attached + * through this input connection. The event will be dispatched + * like a normal key event, to the currently focused view; this + * generally is the view that is providing this InputConnection, + * but due to the asynchronous nature of this protocol that can + * not be guaranteed and the focus may have changed by the time + * the event is received. + * + * <p>This method can be used to send key events to the + * application. For example, an on-screen keyboard may use this + * method to simulate a hardware keyboard. There are three types + * of standard keyboards, numeric (12-key), predictive (20-key) + * and ALPHA (QWERTY). You can specify the keyboard type by + * specify the device id of the key event.</p> + * + * <p>You will usually want to set the flag + * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD} + * on all key event objects you give to this API; the flag will + * not be set for you.</p> + * + * <p>Note that it's discouraged to send such key events in normal + * operation; this is mainly for use with + * {@link android.text.InputType#TYPE_NULL} type text fields. Use + * the {@link #commitText} family of methods to send text to the + * application instead.</p> + * + * @param event The key event. + * + * @see KeyEvent + * @see KeyCharacterMap#NUMERIC + * @see KeyCharacterMap#PREDICTIVE + * @see KeyCharacterMap#ALPHA + */ + public void sendKeyEvent(@NonNull KeyEvent event) { + if (mIc != null) { + mIc.sendKeyEvent(event); + } + } + + /** + * Have the editor perform an action it has said it can do. + * + * @param editorAction This must be one of the action constants for + * {@link EditorInfo#imeOptions EditorInfo.imeOptions}, such as + * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}, or the value of + * {@link EditorInfo#actionId EditorInfo.actionId} if a custom action is available. + */ + public void performEditorAction(int editorAction) { + if (mIc != null) { + mIc.performEditorAction(editorAction); + } + } + + /** + * Perform a context menu action on the field. The given id may be one of: + * {@link android.R.id#selectAll}, + * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText}, + * {@link android.R.id#cut}, {@link android.R.id#copy}, + * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, + * or {@link android.R.id#switchInputMethod} + */ + public void performContextMenuAction(int id) { + if (mIc != null) { + mIc.performContextMenuAction(id); + } + } + + /** + * Retrieve the current capitalization mode in effect at the + * current cursor position in the text. See + * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} + * for more information. + * + * <p>This method may fail either if the input connection has + * become invalid (such as its process crashing) or the client is + * taking too long to respond with the text (it is given a couple + * seconds to return). In either case, 0 is returned.</p> + * + * <p>This method does not affect the text in the editor in any + * way, nor does it affect the selection or composing spans.</p> + * + * <p><strong>Editor authors:</strong> please be careful of race + * conditions in implementing this call. An IME can change the + * cursor position and use this method right away; you need to make + * sure the returned value is consistent with the results of the + * latest edits and changes to the cursor position.</p> + * + * @param reqModes The desired modes to retrieve, as defined by + * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These + * constants are defined so that you can simply pass the current + * {@link EditorInfo#inputType TextBoxAttribute.contentType} value + * directly in to here. + * @return the caps mode flags that are in effect at the current + * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. + */ + public int getCursorCapsMode(int reqModes) { + if (mIc != null) { + return mIc.getCursorCapsMode(reqModes); + } + return 0; + } + + /** + * Clear the given meta key pressed states in the given input + * connection. + * + * <p>This can be used by the accessibility service to clear the meta key states set + * by a hardware keyboard with latched meta keys, if the editor + * keeps track of these.</p> + * + * @param states The states to be cleared, may be one or more bits as + * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}. + */ + public void clearMetaKeyStates(int states) { + if (mIc != null) { + mIc.clearMetaKeyStates(states); + } + } + } + + /** + * Concrete implementation of InputMethodSession that provides all of the standard behavior + * for an input method session. + */ + private final class InputMethodSessionForAccessibility implements InputMethodSession { + boolean mEnabled = true; + + public void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + @Override + public void finishInput() { + if (mEnabled) { + doFinishInput(); + } + } + + @Override + public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, + int newSelEnd, int candidatesStart, int candidatesEnd) { + if (mEnabled) { + InputMethod.this.onUpdateSelection(oldSelEnd, oldSelEnd, newSelStart, + newSelEnd, candidatesStart, candidatesEnd); + } + } + + @Override + public void viewClicked(boolean focusChanged) { + } + + @Override + public void updateCursor(@NonNull Rect newCursor) { + } + + @Override + public void displayCompletions( + @SuppressLint("ArrayReturn") @NonNull CompletionInfo[] completions) { + } + + @Override + public void updateExtractedText(int token, @NonNull ExtractedText text) { + } + + public void dispatchKeyEvent(int seq, @NonNull KeyEvent event, + @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) { + } + + @Override + public void dispatchKeyEvent(int seq, @NonNull KeyEvent event, + @NonNull EventCallback callback) { + } + + public void dispatchTrackballEvent(int seq, @NonNull MotionEvent event, + @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) { + } + + @Override + public void dispatchTrackballEvent(int seq, @NonNull MotionEvent event, + @NonNull EventCallback callback) { + } + + public void dispatchGenericMotionEvent(int seq, @NonNull MotionEvent event, + @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) { + } + + @Override + public void dispatchGenericMotionEvent(int seq, @NonNull MotionEvent event, + @NonNull EventCallback callback) { + } + + @Override + public void appPrivateCommand(@NonNull String action, @NonNull Bundle data) { + } + + @Override + public void toggleSoftInput(int showFlags, int hideFlags) { + } + + @Override + public void updateCursorAnchorInfo(@NonNull CursorAnchorInfo cursorAnchorInfo) { + } + + @Override + public void notifyImeHidden() { + } + + @Override + public void removeImeSurface() { + } + + /** + * {@inheritDoc} + */ + @Override + public void invalidateInputInternal(EditorInfo editorInfo, IInputContext inputContext, + int sessionId) { + // TODO(b/217788708): Add automated test. + if (mStartedInputConnection instanceof RemoteInputConnection) { + final RemoteInputConnection ric = + (RemoteInputConnection) mStartedInputConnection; + if (!ric.isSameConnection(inputContext)) { + // This is not an error, and can be safely ignored. + return; + } + editorInfo.makeCompatible( + mService.getApplicationInfo().targetSdkVersion); + restartInput(new RemoteInputConnection(ric, sessionId), editorInfo); + } + } + } +} diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e7b6646037f5..983dde3beda0 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -47,7 +47,9 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ActivityNotFoundException; +import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; +import android.content.ComponentCallbacksController; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -982,6 +984,8 @@ public class Activity extends ContextThemeWrapper @Nullable private DumpableContainerImpl mDumpableContainer; + private ComponentCallbacksController mCallbacksController; + private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { /** @@ -1323,6 +1327,28 @@ public class Activity extends ContextThemeWrapper } } + @Override + public void registerComponentCallbacks(ComponentCallbacks callback) { + if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS) + && mCallbacksController == null) { + mCallbacksController = new ComponentCallbacksController(); + } + if (mCallbacksController != null) { + mCallbacksController.registerCallbacks(callback); + } else { + super.registerComponentCallbacks(callback); + } + } + + @Override + public void unregisterComponentCallbacks(ComponentCallbacks callback) { + if (mCallbacksController != null) { + mCallbacksController.unregisterCallbacks(callback); + } else { + super.unregisterComponentCallbacks(callback); + } + } + private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) { getApplication().dispatchActivityPreCreated(this, savedInstanceState); Object[] callbacks = collectActivityLifecycleCallbacks(); @@ -2668,9 +2694,12 @@ public class Activity extends ContextThemeWrapper if (mUiTranslationController != null) { mUiTranslationController.onActivityDestroyed(); } - if (mDefaultBackCallback != null) { getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); + mDefaultBackCallback = null; + } + if (mCallbacksController != null) { + mCallbacksController.clearCallbacks(); } } @@ -2991,6 +3020,9 @@ public class Activity extends ContextThemeWrapper } dispatchActivityConfigurationChanged(); + if (mCallbacksController != null) { + mCallbacksController.dispatchConfigurationChanged(newConfig); + } } /** @@ -3162,12 +3194,18 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this); mCalled = true; mFragments.dispatchLowMemory(); + if (mCallbacksController != null) { + mCallbacksController.dispatchLowMemory(); + } } public void onTrimMemory(int level) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level); mCalled = true; mFragments.dispatchTrimMemory(level); + if (mCallbacksController != null) { + mCallbacksController.dispatchTrimMemory(level); + } } /** @@ -8662,8 +8700,18 @@ public class Activity extends ContextThemeWrapper } /** - * If set to true, this indicates to the system that it should never take a - * screenshot of the activity to be used as a representation while it is not in a started state. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S, + publicAlternatives = "Use {@link #setRecentsScreenshotEnabled(boolean)} instead.") + public void setDisablePreviewScreenshots(boolean disable) { + setRecentsScreenshotEnabled(!disable); + } + + /** + * If set to false, this indicates to the system that it should never take a + * screenshot of the activity to be used as a representation in recents screen. By default, this + * value is {@code true}. * <p> * Note that the system may use the window background of the theme instead to represent * the window when it is not running. @@ -8676,12 +8724,10 @@ public class Activity extends ContextThemeWrapper * {@link android.service.voice.VoiceInteractionService} requests a screenshot via * {@link android.service.voice.VoiceInteractionSession#SHOW_WITH_SCREENSHOT}. * - * @param disable {@code true} to disable preview screenshots; {@code false} otherwise. - * @hide + * @param enabled {@code true} to enable recents screenshots; {@code false} otherwise. */ - @UnsupportedAppUsage - public void setDisablePreviewScreenshots(boolean disable) { - ActivityClient.getInstance().setDisablePreviewScreenshots(mToken, disable); + public void setRecentsScreenshotEnabled(boolean enabled) { + ActivityClient.getInstance().setRecentsScreenshotEnabled(mToken, enabled); } /** diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index 605a1fa82254..4715e0f2cec8 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -438,9 +438,9 @@ public class ActivityClient { } } - void setDisablePreviewScreenshots(IBinder token, boolean disable) { + void setRecentsScreenshotEnabled(IBinder token, boolean enabled) { try { - getActivityClientController().setDisablePreviewScreenshots(token, disable); + getActivityClientController().setRecentsScreenshotEnabled(token, enabled); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index a58ceaa99022..cce7dd338b3d 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -215,14 +215,6 @@ public abstract class ActivityManagerInternal { public abstract boolean isSystemReady(); /** - * Returns package name given pid. - * - * @param pid The pid we are searching package name for. - */ - @Nullable - public abstract String getPackageNameByPid(int pid); - - /** * Sets if the given pid has an overlay UI or not. * * @param pid The pid we are setting overlay UI for. diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index e405b60c8daa..acbcb3ea89b5 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1429,7 +1429,6 @@ public class ActivityOptions extends ComponentOptions { /** * Gets the style can be used for cold-launching an activity. * @see #setSplashScreenStyle(int) - * @hide */ public @SplashScreen.SplashScreenStyle int getSplashScreenStyle() { return mSplashScreenStyle; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5b8969e9374e..61d186579f1b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2132,7 +2132,16 @@ public final class ActivityThread extends ClientTransactionHandler Looper.myLooper().quit(); break; case RECEIVER: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp"); + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + ReceiverData rec = (ReceiverData) msg.obj; + if (rec.intent != null) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "broadcastReceiveComp: " + rec.intent.getAction()); + } else { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "broadcastReceiveComp"); + } + } handleReceiver((ReceiverData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 8181a74e8f95..ec2115c2baed 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1998,7 +1998,7 @@ class ContextImpl extends Context { private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, String instanceName, Handler handler, Executor executor, UserHandle user) { // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and - // ActivityManagerService.LocalService.startAndBindSupplementalProcessService + // ActivityManagerLocal.bindSupplementalProcessService IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 208477588885..aa6c1842ddec 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -465,6 +465,7 @@ public class Dialog implements DialogInterface, Window.Callback, } }; getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); + mDefaultBackCallback = null; } } diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index 040399ecb83b..6f49c9e647a4 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -109,6 +109,7 @@ public final class GameManager { * * @hide */ + @TestApi @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int getGameMode(@NonNull String packageName) { diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 396e5528ab0c..f9439cb6d770 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -115,8 +115,8 @@ interface IActivityClientController { int enterAnim, int exitAnim, int backgroundColor); int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName); - /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */ - oneway void setDisablePreviewScreenshots(in IBinder token, boolean disable); + /** See {@link android.app.Activity#setRecentsScreenshotEnabled}. */ + oneway void setRecentsScreenshotEnabled(in IBinder token, boolean enabled); /** * It should only be called from home activity to remove its outdated snapshot. The home diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index c5add66e0a14..cc8b182f3e41 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -130,6 +130,7 @@ interface IActivityTaskManager { in ProfilerInfo profilerInfo, in Bundle options, int userId); int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid, int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY)") int startActivityFromGameSession(IApplicationThread caller, in String callingPackage, in String callingFeatureId, int callingPid, int callingUid, in Intent intent, int taskId, int userId); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index a74438ae8452..e0c69df0aa18 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -381,10 +381,6 @@ public class Instrumentation { * Force the global system in or out of touch mode. This can be used if your * instrumentation relies on the UI being in one more or the other when it starts. * - * <p><b>Note:</b> Starting from Android {@link Build.VERSION_CODES#TIRAMISU}, this method - * will only have an effect if the calling process is also the focused window owner or has - * {@link android.permission#MODIFY_TOUCH_MODE_STATE} permission granted. - * * @param inTouch Set to true to be in touch mode, false to be in focus mode. */ public void setInTouchMode(boolean inTouch) { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 38e344e57294..77c7c6f80f0e 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1734,7 +1734,11 @@ public final class LoadedApk { return; } - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg"); + if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "broadcastReceiveReg: " + intent.getAction()); + } + try { ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 4aa2d2e8a6a0..392f52a08fb5 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2573,11 +2573,18 @@ public class NotificationManager { * method will return false regardless of input. * </p> * <p> - * The provided URI must meet the requirements for a URI associated with a - * {@link Person}: it may be the {@code String} representation of a - * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, or a - * <code>mailto:</code> or <code>tel:</code> schema URI matching an entry in the - * Contacts database. See also {@link Person.Builder#setUri} and + * The provided URI should be a <code>tel:</code> or <code>mailto:</code> schema URI indicating + * the source of the call. For an accurate answer regarding whether the caller matches the + * user's permitted contacts, the path part of the URI must match an entry the Contacts database + * in the appropriate column. + * </p> + * <p> + * Passing in a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} is also + * permissible, but should only be used for priority contact interruptions and may not provide + * accurate results in the case of repeat callers. + * </p> + * <p> + * See also {@link Person.Builder#setUri} and * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} * for more information. * </p> diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index da1ba5265c4a..e8b5c0ddca8c 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -920,7 +920,7 @@ public class StatusBarManager { public void setNavBarModeOverride(@NavBarModeOverride int navBarModeOverride) { if (navBarModeOverride != NAV_BAR_MODE_OVERRIDE_NONE && navBarModeOverride != NAV_BAR_MODE_OVERRIDE_KIDS) { - throw new UnsupportedOperationException( + throw new IllegalArgumentException( "Supplied navBarModeOverride not supported: " + navBarModeOverride); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f5f2fe0d0292..eeb4705fc7d3 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -25,7 +25,7 @@ import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.ambientcontext.AmbientContextManager; -import android.app.ambientcontext.IAmbientContextEventObserver; +import android.app.ambientcontext.IAmbientContextManager; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.cloudsearch.CloudSearchManager; @@ -1542,8 +1542,8 @@ public final class SystemServiceRegistry { throws ServiceNotFoundException { IBinder iBinder = ServiceManager.getServiceOrThrow( Context.AMBIENT_CONTEXT_SERVICE); - IAmbientContextEventObserver manager = - IAmbientContextEventObserver.Stub.asInterface(iBinder); + IAmbientContextManager manager = + IAmbientContextManager.Stub.asInterface(iBinder); return new AmbientContextManager(ctx.getOuterContext(), manager); }}); diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index b41b5f005f1f..2af8905fa3af 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -63,9 +63,14 @@ import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodSession; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.internal.view.IInputSessionWithIdCallback; import libcore.io.IoUtils; @@ -1566,6 +1571,29 @@ public final class UiAutomation { } @Override + public void createImeSession(IInputSessionWithIdCallback callback) { + /* do nothing */ + } + + @Override + public void setImeSessionEnabled(InputMethodSession session, boolean enabled) { + } + + @Override + public void bindInput(InputBinding binding) { + } + + @Override + public void unbindInput() { + } + + @Override + public void startInput(@Nullable InputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting, + @NonNull IBinder startInputToken) { + } + + @Override public boolean onGesture(AccessibilityGestureEvent gestureEvent) { /* do nothing */ return false; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 71d4ab43db02..e52ae5186180 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -720,6 +720,125 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_ALLOW_OFFLINE"; /** + * A String extra holding a url that specifies the download location of the device manager + * role holder package. + * + * <p>This is only meant to be used in cases when a specific variant of the role holder package + * is needed (such as a debug variant). If not provided, the default variant of the device + * manager role holder package is downloaded. + * + * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION = + "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION"; + + /** + * A String extra holding the URL-safe base64 encoded SHA-256 checksum of any signature of the + * android package archive at the download location specified in {@link + * #EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION}. + * + * <p>The signatures of an android package archive can be obtained using + * {@link android.content.pm.PackageManager#getPackageArchiveInfo} with flag + * {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES}. + * + * <p>If {@link #EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION} is provided, it must + * be accompanied by this extra. The provided checksum must match the checksum of any signature + * of the file at the download location. If the checksum does not match an error will be shown + * to the user and the user will be asked to factory reset the device. + * + * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM = + "android.app.extra.PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM"; + + /** + * A String extra holding a http cookie header which should be used in the http request to the + * url specified in {@link #EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION}. + * + * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER = + "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER"; + + /** + * An extra of type {@link android.os.PersistableBundle} that allows the provisioning initiator + * to pass data to the device manager role holder. + * + * <p>The device manager role holder will receive this extra via the {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent. + * + * <p>The contents of this extra are up to the contract between the provisioning initiator + * and the device manager role holder. + * + * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} + * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. + * + * @hide + */ + @SystemApi + public static final String EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE = + "android.app.extra.PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE"; + + /** + * A String extra containing the package name of the provisioning initiator. + * + * <p>Use in an intent with action {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. + * + * @hide + */ + @SystemApi + public static final String EXTRA_ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE = + "android.app.extra.ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE"; + + /** + * An {@link Intent} result extra specifying the {@link Intent} to be launched after + * provisioning is finalized. + * + * <p>If {@link #EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT} is set to {@code false}, + * this result will be supplied as part of the result {@link Intent} for provisioning actions + * such as {@link #ACTION_PROVISION_MANAGED_PROFILE}. This result will also be supplied as + * part of the result {@link Intent} for the device manager role holder provisioning actions. + */ + public static final String EXTRA_RESULT_LAUNCH_INTENT = + "android.app.extra.RESULT_LAUNCH_INTENT"; + + /** + * A boolean extra that determines whether the provisioning flow should launch the resulting + * launch intent, if one is supplied by the device manager role holder via {@link + * #EXTRA_RESULT_LAUNCH_INTENT}. Default value is {@code false}. + * + * <p>If {@code true}, the resulting intent will be launched by the provisioning flow, if one + * is supplied by the device manager role holder. + * + * <p>If {@code false}, the resulting intent will be returned as {@link + * #EXTRA_RESULT_LAUNCH_INTENT} to the provisioning initiator, if one is supplied by the device + * manager role holder. It will be the responsibility of the provisioning initiator to launch + * this {@link Intent} after provisioning completes. + * + * <p>This extra is respected when provided via the provisioning intent actions such as {@link + * #ACTION_PROVISION_MANAGED_PROFILE}. + */ + public static final String EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT = + "android.app.extra.PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT"; + + /** * Action: Bugreport sharing with device owner has been accepted by the user. * * @hide @@ -3585,10 +3704,11 @@ public class DevicePolicyManager { @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @UserHandleAware public void acknowledgeNewUserDisclaimer() { if (mService != null) { try { - mService.acknowledgeNewUserDisclaimer(); + mService.acknowledgeNewUserDisclaimer(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3596,17 +3716,18 @@ public class DevicePolicyManager { } /** - * Checks whether the new managed user disclaimer was viewed by the current user. + * Checks whether the new managed user disclaimer was viewed by the user. * * @hide */ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) @TestApi + @UserHandleAware public boolean isNewUserDisclaimerAcknowledged() { if (mService != null) { try { - return mService.isNewUserDisclaimerAcknowledged(); + return mService.isNewUserDisclaimerAcknowledged(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -15516,4 +15637,40 @@ public class DevicePolicyManager { } } } + + /** + * Returns the package name of the device manager role holder. + * + * <p>If the device manager role holder is not configured for this device, returns {@code null}. + */ + @Nullable + public String getDeviceManagerRoleHolderPackageName() { + String deviceManagerConfig = + mContext.getString(com.android.internal.R.string.config_deviceManager); + return extractPackageNameFromDeviceManagerConfig(deviceManagerConfig); + } + + /** + * Retrieves the package name for a given {@code deviceManagerConfig}. + * + * <p>Valid configs look like: + * <ul> + * <li>{@code com.package.name}</li> + * <li>{@code com.package.name:<SHA256 checksum>}</li> + * </ul> + * + * <p>If the supplied {@code deviceManagerConfig} is {@code null} or empty, returns + * {@code null}. + */ + @Nullable + private String extractPackageNameFromDeviceManagerConfig( + @Nullable String deviceManagerConfig) { + if (TextUtils.isEmpty(deviceManagerConfig)) { + return null; + } + if (deviceManagerConfig.contains(":")) { + return deviceManagerConfig.split(":")[0]; + } + return deviceManagerConfig; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 0e1caca2670a..eedc042607be 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -271,8 +271,8 @@ interface IDevicePolicyManager { int logoutUserInternal(); // AIDL doesn't allow overloading name (logoutUser()) int getLogoutUserId(); List<UserHandle> getSecondaryUsers(in ComponentName who); - void acknowledgeNewUserDisclaimer(); - boolean isNewUserDisclaimerAcknowledged(); + void acknowledgeNewUserDisclaimer(int userId); + boolean isNewUserDisclaimerAcknowledged(int userId); void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName); int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent); diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java index 82b16a2db0ce..0557acb1130d 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java @@ -23,6 +23,9 @@ import android.os.Parcelable; import android.os.PersistableBundle; import android.util.ArraySet; +import com.android.internal.util.AnnotationValidations; +import com.android.internal.util.Preconditions; + import java.util.HashSet; import java.util.Set; @@ -36,15 +39,17 @@ public final class AmbientContextEventRequest implements Parcelable { @NonNull private final Set<Integer> mEventTypes; @NonNull private final PersistableBundle mOptions; - AmbientContextEventRequest( + private AmbientContextEventRequest( @NonNull Set<Integer> eventTypes, @NonNull PersistableBundle options) { this.mEventTypes = eventTypes; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mEventTypes); + AnnotationValidations.validate(NonNull.class, null, mEventTypes); + Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty"); + for (int eventType : eventTypes) { + AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType); + } this.mOptions = options; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mOptions); + AnnotationValidations.validate(NonNull.class, null, mOptions); } /** @@ -80,16 +85,20 @@ public final class AmbientContextEventRequest implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) - AmbientContextEventRequest(@NonNull Parcel in) { + private AmbientContextEventRequest(@NonNull Parcel in) { Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader()); PersistableBundle options = (PersistableBundle) in.readTypedObject( PersistableBundle.CREATOR); this.mEventTypes = eventTypes; - com.android.internal.util.AnnotationValidations.validate( + AnnotationValidations.validate( NonNull.class, null, mEventTypes); + Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty"); + for (int eventType : eventTypes) { + AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType); + } this.mOptions = options; - com.android.internal.util.AnnotationValidations.validate( + AnnotationValidations.validate( NonNull.class, null, mOptions); } diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java deleted file mode 100644 index 472a78b177c9..000000000000 --- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2022 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 android.app.ambientcontext; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.app.PendingIntent; -import android.os.Parcelable; - -import com.android.internal.util.AnnotationValidations; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents a response from the {@code AmbientContextEvent} service. - * - * @hide - */ -@SystemApi -public final class AmbientContextEventResponse implements Parcelable { - /** - * An unknown status. - */ - public static final int STATUS_UNKNOWN = 0; - /** - * The value of the status code that indicates success. - */ - public static final int STATUS_SUCCESS = 1; - /** - * The value of the status code that indicates one or more of the - * requested events are not supported. - */ - public static final int STATUS_NOT_SUPPORTED = 2; - /** - * The value of the status code that indicates service not available. - */ - public static final int STATUS_SERVICE_UNAVAILABLE = 3; - /** - * The value of the status code that microphone is disabled. - */ - public static final int STATUS_MICROPHONE_DISABLED = 4; - /** - * The value of the status code that the app is not granted access. - */ - public static final int STATUS_ACCESS_DENIED = 5; - - /** @hide */ - @IntDef(prefix = { "STATUS_" }, value = { - STATUS_UNKNOWN, - STATUS_SUCCESS, - STATUS_NOT_SUPPORTED, - STATUS_SERVICE_UNAVAILABLE, - STATUS_MICROPHONE_DISABLED, - STATUS_ACCESS_DENIED - }) public @interface StatusCode {} - - @StatusCode private final int mStatusCode; - @NonNull private final List<AmbientContextEvent> mEvents; - @NonNull private final String mPackageName; - @Nullable private final PendingIntent mActionPendingIntent; - - /** @hide */ - public static String statusToString(@StatusCode int value) { - switch (value) { - case STATUS_UNKNOWN: - return "STATUS_UNKNOWN"; - case STATUS_SUCCESS: - return "STATUS_SUCCESS"; - case STATUS_NOT_SUPPORTED: - return "STATUS_NOT_SUPPORTED"; - case STATUS_SERVICE_UNAVAILABLE: - return "STATUS_SERVICE_UNAVAILABLE"; - case STATUS_MICROPHONE_DISABLED: - return "STATUS_MICROPHONE_DISABLED"; - case STATUS_ACCESS_DENIED: - return "STATUS_ACCESS_DENIED"; - default: return Integer.toHexString(value); - } - } - - AmbientContextEventResponse( - @StatusCode int statusCode, - @NonNull List<AmbientContextEvent> events, - @NonNull String packageName, - @Nullable PendingIntent actionPendingIntent) { - this.mStatusCode = statusCode; - AnnotationValidations.validate(StatusCode.class, null, mStatusCode); - this.mEvents = events; - AnnotationValidations.validate(NonNull.class, null, mEvents); - this.mPackageName = packageName; - AnnotationValidations.validate(NonNull.class, null, mPackageName); - this.mActionPendingIntent = actionPendingIntent; - } - - /** - * The status of the response. - */ - public @StatusCode int getStatusCode() { - return mStatusCode; - } - - /** - * The detected event. - */ - public @NonNull List<AmbientContextEvent> getEvents() { - return mEvents; - } - - /** - * The package to deliver the response to. - */ - public @NonNull String getPackageName() { - return mPackageName; - } - - /** - * A {@link PendingIntent} that the client should call to allow further actions by user. - * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the - * grant access activity. - */ - public @Nullable PendingIntent getActionPendingIntent() { - return mActionPendingIntent; - } - - @Override - public String toString() { - return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", " - + "events = " + mEvents + ", " + "packageName = " + mPackageName + ", " - + "callbackPendingIntent = " + mActionPendingIntent + " }"; - } - - @Override - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - byte flg = 0; - if (mActionPendingIntent != null) flg |= 0x8; - dest.writeByte(flg); - dest.writeInt(mStatusCode); - dest.writeParcelableList(mEvents, flags); - dest.writeString(mPackageName); - if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags); - } - - @Override - public int describeContents() { - return 0; - } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - AmbientContextEventResponse(@NonNull android.os.Parcel in) { - byte flg = in.readByte(); - int statusCode = in.readInt(); - List<AmbientContextEvent> events = new ArrayList<>(); - in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(), - AmbientContextEvent.class); - String packageName = in.readString(); - PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null - : (PendingIntent) in.readTypedObject(PendingIntent.CREATOR); - - this.mStatusCode = statusCode; - AnnotationValidations.validate( - StatusCode.class, null, mStatusCode); - this.mEvents = events; - AnnotationValidations.validate( - NonNull.class, null, mEvents); - this.mPackageName = packageName; - AnnotationValidations.validate( - NonNull.class, null, mPackageName); - this.mActionPendingIntent = callbackPendingIntent; - } - - public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR = - new Parcelable.Creator<AmbientContextEventResponse>() { - @Override - public AmbientContextEventResponse[] newArray(int size) { - return new AmbientContextEventResponse[size]; - } - - @Override - public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) { - return new AmbientContextEventResponse(in); - } - }; - - /** - * A builder for {@link AmbientContextEventResponse} - */ - @SuppressWarnings("WeakerAccess") - public static final class Builder { - private @StatusCode int mStatusCode; - private @NonNull List<AmbientContextEvent> mEvents; - private @NonNull String mPackageName; - private @Nullable PendingIntent mCallbackPendingIntent; - private long mBuilderFieldsSet = 0L; - - public Builder() { - } - - /** - * The status of the response. - */ - public @NonNull Builder setStatusCode(@StatusCode int value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mStatusCode = value; - return this; - } - - /** - * Adds an event to the builder. - */ - public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) { - checkNotUsed(); - if (mEvents == null) { - mBuilderFieldsSet |= 0x2; - mEvents = new ArrayList<>(); - } - mEvents.add(value); - return this; - } - - /** - * The package to deliver the response to. - */ - public @NonNull Builder setPackageName(@NonNull String value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; - mPackageName = value; - return this; - } - - /** - * A {@link PendingIntent} that the client should call to allow further actions by user. - * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to - * the grant access activity. - */ - public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x8; - mCallbackPendingIntent = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull AmbientContextEventResponse build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x10; // Mark builder used - - if ((mBuilderFieldsSet & 0x1) == 0) { - mStatusCode = STATUS_UNKNOWN; - } - if ((mBuilderFieldsSet & 0x2) == 0) { - mEvents = new ArrayList<>(); - } - if ((mBuilderFieldsSet & 0x4) == 0) { - mPackageName = ""; - } - if ((mBuilderFieldsSet & 0x8) == 0) { - mCallbackPendingIntent = null; - } - AmbientContextEventResponse o = new AmbientContextEventResponse( - mStatusCode, - mEvents, - mPackageName, - mCallbackPendingIntent); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x10) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } -} diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index 6841d1bbfc1f..7f913e798bc9 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -17,116 +17,280 @@ package android.app.ambientcontext; import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Binder; +import android.os.RemoteCallback; import android.os.RemoteException; import com.android.internal.util.Preconditions; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + /** - * Allows granted apps to register for particular pre-defined {@link AmbientContextEvent}s. - * After successful registration, the app receives a callback on the provided {@link PendingIntent} - * when the requested event is detected. - * <p /> - * - * Example: - * - * <pre><code> - * // Create request - * AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() - * .addEventType(AmbientContextEvent.EVENT_COUGH) - * .addEventTYpe(AmbientContextEvent.EVENT_SNORE) - * .build(); - * // Create PendingIntent - * Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class) - * .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - * PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0); - * // Register for events - * AmbientContextManager ambientContextManager = - * context.getSystemService(AmbientContextManager.class); - * ambientContextManager.registerObserver(request, pendingIntent); - * - * // Handle the callback intent in your receiver - * {@literal @}Override - * protected void onReceive(Context context, Intent intent) { - * AmbientContextEventResponse response = - * AmbientContextManager.getResponseFromIntent(intent); - * if (response != null) { - * if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) { - * // Do something useful with response.getEvent() - * } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) { - * // Redirect users to grant access - * PendingIntent callbackPendingIntent = response.getCallbackPendingIntent(); - * if (callbackPendingIntent != null) { - * callbackPendingIntent.send(); - * } - * } else ... - * } - * } - * </code></pre> + * Allows granted apps to register for event types defined in {@link AmbientContextEvent}. + * After registration, the app receives a Consumer callback of the service status. + * If it is {@link STATUS_SUCCESSFUL}, when the requested events are detected, the provided + * {@link PendingIntent} callback will receive the list of detected {@link AmbientContextEvent}s. + * If it is {@link STATUS_ACCESS_DENIED}, the app can call {@link #startConsentActivity} + * to load the consent screen. * * @hide */ @SystemApi @SystemService(Context.AMBIENT_CONTEXT_SERVICE) public final class AmbientContextManager { + /** + * The bundle key for the service status query result, used in + * {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String STATUS_RESPONSE_BUNDLE_KEY = + "android.app.ambientcontext.AmbientContextStatusBundleKey"; + + /** + * The key of an intent extra indicating a list of detected {@link AmbientContextEvent}s. + * The intent is sent to the app in the app's registered {@link PendingIntent}. + */ + public static final String EXTRA_AMBIENT_CONTEXT_EVENTS = + "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS"; + + /** + * An unknown status. + */ + public static final int STATUS_UNKNOWN = 0; + + /** + * The value of the status code that indicates success. + */ + public static final int STATUS_SUCCESS = 1; + + /** + * The value of the status code that indicates one or more of the + * requested events are not supported. + */ + public static final int STATUS_NOT_SUPPORTED = 2; /** - * The key of an Intent extra indicating the response. + * The value of the status code that indicates service not available. */ - public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = - "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + public static final int STATUS_SERVICE_UNAVAILABLE = 3; /** - * Allows clients to retrieve the response from the intent. + * The value of the status code that microphone is disabled. + */ + public static final int STATUS_MICROPHONE_DISABLED = 4; + + /** + * The value of the status code that the app is not granted access. + */ + public static final int STATUS_ACCESS_DENIED = 5; + + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_UNKNOWN, + STATUS_SUCCESS, + STATUS_NOT_SUPPORTED, + STATUS_SERVICE_UNAVAILABLE, + STATUS_MICROPHONE_DISABLED, + STATUS_ACCESS_DENIED + }) public @interface StatusCode {} + + /** + * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent. + * * @param intent received from the PendingIntent callback * - * @return the AmbientContextEventResponse, or null if not present + * @return the list of events, or an empty list if the intent doesn't have such events. */ - @Nullable - public static AmbientContextEventResponse getResponseFromIntent( - @NonNull Intent intent) { - if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) { - return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE); + @NonNull public static List<AmbientContextEvent> getEventsFromIntent(@NonNull Intent intent) { + if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) { + return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS); } else { - return null; + return new ArrayList<>(); } } private final Context mContext; - private final IAmbientContextEventObserver mService; + private final IAmbientContextManager mService; /** * {@hide} */ - public AmbientContextManager(Context context, IAmbientContextEventObserver service) { + public AmbientContextManager(Context context, IAmbientContextManager service) { mContext = context; mService = service; } /** + * Queries the {@link AmbientContextEvent} service status for the calling package, and + * sends the result to the {@link Consumer} right after the call. This is used by foreground + * apps to check whether the requested events are enabled for detection on the device. + * If all events are enabled for detection, the response has + * {@link AmbientContextManager#STATUS_SUCCESS}. + * If any of the events are not consented by user, the response has + * {@link AmbientContextManager#STATUS_ACCESS_DENIED}, and the app can + * call {@link #startConsentActivity} to redirect the user to the consent screen. + * <p /> + * + * Example: + * + * <pre><code> + * Set<Integer> eventTypes = new HashSet<>(); + * eventTypes.add(AmbientContextEvent.EVENT_COUGH); + * eventTypes.add(AmbientContextEvent.EVENT_SNORE); + * + * // Create Consumer + * Consumer<Integer> statusConsumer = response -> { + * int status = status.getStatusCode(); + * if (status == AmbientContextManager.STATUS_SUCCESS) { + * // Show user it's enabled + * } else if (status == AmbientContextManager.STATUS_ACCESS_DENIED) { + * // Send user to grant access + * startConsentActivity(eventTypes); + * } + * }; + * + * // Query status + * AmbientContextManager ambientContextManager = + * context.getSystemService(AmbientContextManager.class); + * ambientContextManager.queryAmbientContextStatus(eventTypes, executor, statusConsumer); + * </code></pre> + * + * @param eventTypes The set of event codes to check status on. + * @param executor Executor on which to run the consumer callback. + * @param consumer The consumer that handles the status code. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void queryAmbientContextServiceStatus( + @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> consumer) { + try { + RemoteCallback callback = new RemoteCallback(result -> { + int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> consumer.accept(status)); + } finally { + Binder.restoreCallingIdentity(identity); + } + }); + mService.queryServiceStatus(integerSetToIntArray(eventTypes), + mContext.getOpPackageName(), callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests the consent data host to open an activity that allows users to modify consent. + * + * @param eventTypes The set of event codes to be consented. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void startConsentActivity( + @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes) { + try { + mService.startConsentActivity( + integerSetToIntArray(eventTypes), mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @NonNull + private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) { + int[] intArray = new int[integerSet.size()]; + int i = 0; + for (Integer type : integerSet) { + intArray[i++] = type; + } + return intArray; + } + + /** * Allows app to register as a {@link AmbientContextEvent} observer. The * observer receives a callback on the provided {@link PendingIntent} when the requested * event is detected. Registering another observer from the same package that has already been * registered will override the previous observer. + * <p /> + * + * Example: + * + * <pre><code> + * // Create request + * AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() + * .addEventType(AmbientContextEvent.EVENT_COUGH) + * .addEventType(AmbientContextEvent.EVENT_SNORE) + * .build(); + * + * // Create PendingIntent for delivering detection results to my receiver + * Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class) + * .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + * PendingIntent pendingIntent = + * PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + * + * // Create Consumer of service status + * Consumer<Integer> statusConsumer = status -> { + * if (status == AmbientContextManager.STATUS_ACCESS_DENIED) { + * // User did not consent event detection. See #queryAmbientContextServiceStatus and + * // #startConsentActivity + * } + * }; + * + * // Register as observer + * AmbientContextManager ambientContextManager = + * context.getSystemService(AmbientContextManager.class); + * ambientContextManager.registerObserver(request, pendingIntent, executor, statusConsumer); + * + * // Handle the list of {@link AmbientContextEvent}s in your receiver + * {@literal @}Override + * protected void onReceive(Context context, Intent intent) { + * List<AmbientContextEvent> events = AmbientContextManager.getEventsFromIntent(intent); + * if (!events.isEmpty()) { + * // Do something useful with the events. + * } + * } + * </code></pre> * * @param request The request with events to observe. - * @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any - * requested event is detected. + * @param resultPendingIntent A mutable {@link PendingIntent} that will be dispatched after the + * requested events are detected. + * @param executor Executor on which to run the consumer callback. + * @param statusConsumer A consumer that handles the status code, which is returned + * right after the call. */ @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver( @NonNull AmbientContextEventRequest request, - @NonNull PendingIntent pendingIntent) { - Preconditions.checkArgument(!pendingIntent.isImmutable()); + @NonNull PendingIntent resultPendingIntent, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + Preconditions.checkArgument(!resultPendingIntent.isImmutable()); try { - mService.registerObserver(request, pendingIntent); + RemoteCallback callback = new RemoteCallback(result -> { + int statusCode = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> statusConsumer.accept(statusCode)); + } finally { + Binder.restoreCallingIdentity(identity); + } + }); + mService.registerObserver(request, resultPendingIntent, callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl index 9032fe1ee045..3b15bcb9846d 100644 --- a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl +++ b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl @@ -18,13 +18,19 @@ package android.app.ambientcontext; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEventRequest; +import android.os.RemoteCallback; /** - * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents. + * Interface for an AmbientContextManager that provides access to AmbientContextEvents. * * @hide */ -oneway interface IAmbientContextEventObserver { - void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent); +oneway interface IAmbientContextManager { + void registerObserver(in AmbientContextEventRequest request, + in PendingIntent resultPendingIntent, + in RemoteCallback statusCallback); void unregisterObserver(in String callingPackage); + void queryServiceStatus(in int[] eventTypes, in String callingPackage, + in RemoteCallback statusCallback); + void startConsentActivity(in int[] eventTypes, in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/app/assist/ActivityId.java b/core/java/android/app/assist/ActivityId.java index fb0d056c2a34..1cc4b024d42d 100644 --- a/core/java/android/app/assist/ActivityId.java +++ b/core/java/android/app/assist/ActivityId.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.IBinder; import android.os.Parcel; +import android.os.Parcelable; import android.service.contentcapture.ContentCaptureService; import android.view.contentcapture.ContentCaptureContext; import android.view.translation.UiTranslationManager; @@ -38,7 +39,8 @@ import com.android.internal.annotations.Immutable; */ @Immutable @SystemApi -public class ActivityId { +@TestApi +public final class ActivityId implements Parcelable { /** * The identifier of the task this activity is in. @@ -53,6 +55,7 @@ public class ActivityId { /** * @hide */ + @TestApi public ActivityId(int taskId, @Nullable IBinder activityId) { mTaskId = taskId; mActivityId = activityId; @@ -87,13 +90,39 @@ public class ActivityId { } /** - * @hide + * {@inheritDoc} + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} */ - public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mTaskId); dest.writeStrongBinder(mActivityId); } + /** + * Creates {@link ActivityId} instances from parcels. + */ + @NonNull + public static final Parcelable.Creator<ActivityId> CREATOR = + new Parcelable.Creator<ActivityId>() { + @Override + public ActivityId createFromParcel(Parcel parcel) { + return new ActivityId(parcel); + } + + @Override + public ActivityId[] newArray(int size) { + return new ActivityId[size]; + } + }; + @Override public String toString() { return "ActivityId { taskId = " + mTaskId + ", activityId = " + mActivityId + " }"; diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 373a8d957282..f7f0235cd508 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -207,6 +207,18 @@ public final class AssociationInfo implements Parcelable { return macAddress.equals(mDeviceMacAddress); } + /** + * Utility method to be used by CdmService only. + * + * @return whether CdmService should bind the companion application that "owns" this association + * when the device is present. + * + * @hide + */ + public boolean shouldBindWhenPresent() { + return mNotifyOnDeviceNearby || mSelfManaged; + } + /** @hide */ public @NonNull String toShortString() { final StringBuilder sb = new StringBuilder(); diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index bd8ba9e03a10..257530b26eec 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -260,9 +260,9 @@ public final class AssociationRequest implements Parcelable { } /** - * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit - * confirmation from the user before creating an association, even if such confirmation is not - * required. + * Indicates whether the application requires the {@link CompanionDeviceManager} service to + * collect an explicit confirmation from the user before creating an association, even if + * such confirmation is not required from the service's perspective. * * @see Builder#setForceConfirmation(boolean) */ @@ -391,9 +391,9 @@ public final class AssociationRequest implements Parcelable { } /** - * Indicates whether the application would prefer the CompanionDeviceManager to collect an - * explicit confirmation from the user before creating an association, even if such - * confirmation is not required. + * Indicates whether the application requires the {@link CompanionDeviceManager} service to + * collect an explicit confirmation from the user before creating an association, even if + * such confirmation is not required from the service's perspective. */ @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) @NonNull diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 8fc24fd2d7f2..e2859998efd4 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -17,6 +17,7 @@ package android.companion.virtual; import android.app.PendingIntent; +import android.companion.virtual.audio.IAudioSessionCallback; import android.graphics.Point; import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; @@ -45,6 +46,15 @@ interface IVirtualDevice { */ void close(); + /** + * Notifies of an audio session being started. + */ + void onAudioSessionStarting( + int displayId, + IAudioSessionCallback callback); + + void onAudioSessionEnded(); + void createVirtualKeyboard( int displayId, String inputDeviceName, diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 1dbe04cfda8e..fdff27fb29f2 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -25,6 +25,8 @@ import android.annotation.SystemService; import android.app.Activity; import android.app.PendingIntent; import android.companion.AssociationInfo; +import android.companion.virtual.audio.VirtualAudioDevice; +import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; @@ -263,8 +265,8 @@ public final class VirtualDeviceManager { * * @param display the display that the events inputted through this device should target * @param inputDeviceName the name to call this input device - * @param vendorId the vendor id - * @param productId the product id + * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id) + * @param productId the product id, as defined by uinput's uinput_setup struct */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -289,8 +291,8 @@ public final class VirtualDeviceManager { * * @param display the display that the events inputted through this device should target * @param inputDeviceName the name to call this input device - * @param vendorId the vendor id - * @param productId the product id + * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id) + * @param productId the product id, as defined by uinput's uinput_setup struct */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -315,8 +317,8 @@ public final class VirtualDeviceManager { * * @param display the display that the events inputted through this device should target * @param inputDeviceName the name to call this input device - * @param vendorId the vendor id - * @param productId the product id + * @param vendorId the vendor id, as defined by uinput's uinput_setup struct (PCI vendor id) + * @param productId the product id, as defined by uinput's uinput_setup struct */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull @@ -339,6 +341,30 @@ public final class VirtualDeviceManager { } /** + * Creates a VirtualAudioDevice, capable of recording audio emanating from this device, + * or injecting audio from another device. + * + * <p>Note: This object does not support capturing privileged playback, such as voice call + * audio. + * + * @param display The target virtual display to capture from and inject into. + * @param executor The {@link Executor} object for the thread on which to execute + * the callback. If <code>null</code>, the {@link Executor} associated with + * the main {@link Looper} will be used. + * @param callback Interface to be notified when playback or recording configuration of + * applications running on virtual display is changed. + * @return A {@link VirtualAudioDevice} instance. + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualAudioDevice createVirtualAudioDevice( + @NonNull VirtualDisplay display, + @Nullable Executor executor, + @Nullable AudioConfigurationChangeCallback callback) { + return new VirtualAudioDevice(mContext, mVirtualDevice, display, executor, callback); + } + + /** * Sets the visibility of the pointer icon for this VirtualDevice's associated displays. * * @param showPointerIcon True if the pointer should be shown; false otherwise. The default diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 1d0f7c091807..45d0ad51da04 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -48,15 +48,16 @@ public final class VirtualDeviceParams implements Parcelable { /** @hide */ @IntDef(prefix = "LOCK_STATE_", - value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED}) + value = {LOCK_STATE_DEFAULT, LOCK_STATE_ALWAYS_UNLOCKED}) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface LockState {} /** - * Indicates that the lock state of the virtual device should be always locked. + * Indicates that the lock state of the virtual device will be the same as the default physical + * display. */ - public static final int LOCK_STATE_ALWAYS_LOCKED = 0; + public static final int LOCK_STATE_DEFAULT = 0; /** * Indicates that the lock state of the virtual device should be always unlocked. @@ -199,7 +200,7 @@ public final class VirtualDeviceParams implements Parcelable { */ public static final class Builder { - private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED; + private @LockState int mLockState = LOCK_STATE_DEFAULT; private Set<UserHandle> mUsersWithMatchingAccounts; @Nullable private Set<ComponentName> mBlockedActivities; @Nullable private Set<ComponentName> mAllowedActivities; @@ -207,9 +208,9 @@ public final class VirtualDeviceParams implements Parcelable { /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}. - * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}. + * The default is {@link #LOCK_STATE_DEFAULT}. * - * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or + * @param lockState The lock state, either {@link #LOCK_STATE_DEFAULT} or * {@link #LOCK_STATE_ALWAYS_UNLOCKED}. */ @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true) diff --git a/core/java/android/companion/virtual/audio/AudioCapture.java b/core/java/android/companion/virtual/audio/AudioCapture.java new file mode 100644 index 000000000000..ebe17dba5775 --- /dev/null +++ b/core/java/android/companion/virtual/audio/AudioCapture.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual.audio; + +import static android.media.AudioRecord.RECORDSTATE_RECORDING; +import static android.media.AudioRecord.RECORDSTATE_STOPPED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.media.AudioRecord; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.nio.ByteBuffer; + +/** + * Wrapper around {@link AudioRecord} that allows for the underlying {@link AudioRecord} to + * be swapped out while recording is ongoing. + * + * @hide + */ +// The stop() actually doesn't release resources, so should not force implementing Closeable. +@SuppressLint("NotCloseable") +@SystemApi +public final class AudioCapture { + private static final String TAG = "AudioCapture"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + @Nullable + private AudioRecord mAudioRecord; + + @GuardedBy("mLock") + private int mRecordingState = RECORDSTATE_STOPPED; + + /** + * Sets the {@link AudioRecord} to handle audio capturing. + * Callers may call this multiple times with different audio records to change + * the underlying {@link AudioRecord} without stopping and re-starting recording. + * + * @param audioRecord The underlying {@link AudioRecord} to use for capture, + * or null if no audio (i.e. silence) should be captured while still keeping the + * record in a recording state. + */ + void setAudioRecord(@Nullable AudioRecord audioRecord) { + Log.d(TAG, "set AudioRecord with " + audioRecord); + synchronized (mLock) { + // Release old reference. + if (mAudioRecord != null) { + mAudioRecord.release(); + } + // Sync recording state for new reference. + if (audioRecord != null) { + if (mRecordingState == RECORDSTATE_RECORDING + && audioRecord.getRecordingState() != RECORDSTATE_RECORDING) { + audioRecord.startRecording(); + } + if (mRecordingState == RECORDSTATE_STOPPED + && audioRecord.getRecordingState() != RECORDSTATE_STOPPED) { + audioRecord.stop(); + } + } + mAudioRecord = audioRecord; + } + } + + /** See {@link AudioRecord#read(ByteBuffer, int)}. */ + public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) { + final int sizeRead; + synchronized (mLock) { + if (mAudioRecord != null) { + sizeRead = mAudioRecord.read(audioBuffer, sizeInBytes); + } else { + sizeRead = 0; + } + } + return sizeRead; + } + + /** See {@link AudioRecord#startRecording()}. */ + public void startRecording() { + synchronized (mLock) { + mRecordingState = RECORDSTATE_RECORDING; + if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_RECORDING) { + mAudioRecord.startRecording(); + } + } + } + + /** See {@link AudioRecord#stop()}. */ + public void stop() { + synchronized (mLock) { + mRecordingState = RECORDSTATE_STOPPED; + if (mAudioRecord != null && mAudioRecord.getRecordingState() != RECORDSTATE_STOPPED) { + mAudioRecord.stop(); + } + } + } + + /** See {@link AudioRecord#getRecordingState()}. */ + public int getRecordingState() { + synchronized (mLock) { + return mRecordingState; + } + } +} diff --git a/core/java/android/companion/virtual/audio/AudioInjection.java b/core/java/android/companion/virtual/audio/AudioInjection.java new file mode 100644 index 000000000000..5e8e0a487a2e --- /dev/null +++ b/core/java/android/companion/virtual/audio/AudioInjection.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual.audio; + +import static android.media.AudioTrack.PLAYSTATE_PLAYING; +import static android.media.AudioTrack.PLAYSTATE_STOPPED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.media.AudioTrack; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.nio.ByteBuffer; + +/** + * Wrapper around {@link AudioTrack} that allows for the underlying {@link AudioTrack} to + * be swapped out while playout is ongoing. + * + * @hide + */ +// The stop() actually doesn't release resources, so should not force implementing Closeable. +@SuppressLint("NotCloseable") +@SystemApi +public final class AudioInjection { + private static final String TAG = "AudioInjection"; + + private final Object mLock = new Object(); + @GuardedBy("mLock") + @Nullable + private AudioTrack mAudioTrack; + @GuardedBy("mLock") + private int mPlayState = PLAYSTATE_STOPPED; + @GuardedBy("mLock") + private boolean mIsSilent; + + /** Sets if the injected microphone sound is silent. */ + void setSilent(boolean isSilent) { + synchronized (mLock) { + mIsSilent = isSilent; + } + } + + /** + * Sets the {@link AudioTrack} to handle audio injection. + * Callers may call this multiple times with different audio tracks to change + * the underlying {@link AudioTrack} without stopping and re-starting injection. + * + * @param audioTrack The underlying {@link AudioTrack} to use for injection, + * or null if no audio (i.e. silence) should be injected while still keeping the + * record in a playing state. + */ + void setAudioTrack(@Nullable AudioTrack audioTrack) { + Log.d(TAG, "set AudioTrack with " + audioTrack); + synchronized (mLock) { + // Release old reference. + if (mAudioTrack != null) { + mAudioTrack.release(); + } + // Sync play state for new reference. + if (audioTrack != null) { + if (mPlayState == PLAYSTATE_PLAYING + && audioTrack.getPlayState() != PLAYSTATE_PLAYING) { + audioTrack.play(); + } + if (mPlayState == PLAYSTATE_STOPPED + && audioTrack.getPlayState() != PLAYSTATE_STOPPED) { + audioTrack.stop(); + } + } + mAudioTrack = audioTrack; + } + } + + /** See {@link AudioTrack#write(ByteBuffer, int, int)}. */ + public int write(@NonNull ByteBuffer audioBuffer, int sizeInBytes, int writeMode) { + final int sizeWrite; + synchronized (mLock) { + if (mAudioTrack != null && !mIsSilent) { + sizeWrite = mAudioTrack.write(audioBuffer, sizeInBytes, writeMode); + } else { + sizeWrite = 0; + } + } + return sizeWrite; + } + + /** See {@link AudioTrack#play()}. */ + public void play() { + synchronized (mLock) { + mPlayState = PLAYSTATE_PLAYING; + if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_PLAYING) { + mAudioTrack.play(); + } + } + } + + /** See {@link AudioTrack#stop()}. */ + public void stop() { + synchronized (mLock) { + mPlayState = PLAYSTATE_STOPPED; + if (mAudioTrack != null && mAudioTrack.getPlayState() != PLAYSTATE_STOPPED) { + mAudioTrack.stop(); + } + } + } + + /** See {@link AudioTrack#getPlayState()}. */ + public int getPlayState() { + synchronized (mLock) { + return mPlayState; + } + } +} diff --git a/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl b/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl new file mode 100644 index 000000000000..e22ce148a9be --- /dev/null +++ b/core/java/android/companion/virtual/audio/IAudioSessionCallback.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.audio; + +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; + +/** + * Callback to control audio rerouting, notify playback and recording state of applications running + * on virtual device. + * + * @hide + */ +oneway interface IAudioSessionCallback { + + /** Updates the set of applications that need to have their audio rerouted. */ + void onAppsNeedingAudioRoutingChanged(in int[] appUids); + + /** + * Called whenever the playback configuration of applications running on virtual device has + * changed. + */ + void onPlaybackConfigChanged(in List<AudioPlaybackConfiguration> configs); + + /** + * Called whenever the recording configuration of applications running on virtual device has + * changed. + */ + void onRecordingConfigChanged(in List<AudioRecordingConfiguration> configs); +} diff --git a/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java new file mode 100644 index 000000000000..5c246d365751 --- /dev/null +++ b/core/java/android/companion/virtual/audio/UserRestrictionsDetector.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual.audio; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.UserManager; + +import com.android.internal.annotations.GuardedBy; + +/** + * Class to detect the user restrictions change for microphone usage. + */ +final class UserRestrictionsDetector extends BroadcastReceiver { + private static final String TAG = "UserRestrictionsDetector"; + + /** Interface for listening user restrictions change. */ + interface UserRestrictionsCallback { + + /** Notifies when value of {@link UserManager#DISALLOW_UNMUTE_MICROPHONE} is changed. */ + void onMicrophoneRestrictionChanged(boolean isUnmuteMicDisallowed); + } + + private final Context mContext; + private final UserManager mUserManager; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private boolean mIsUnmuteMicDisallowed; + private UserRestrictionsCallback mUserRestrictionsCallback; + + UserRestrictionsDetector(Context context) { + mContext = context; + mUserManager = context.getSystemService(UserManager.class); + } + + /** Returns value of {@link UserManager#DISALLOW_UNMUTE_MICROPHONE}. */ + boolean isUnmuteMicrophoneDisallowed() { + Bundle bundle = mUserManager.getUserRestrictions(); + return bundle.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE); + } + + /** Registers user restrictions change. */ + void register(@NonNull UserRestrictionsCallback callback) { + mUserRestrictionsCallback = callback; + + IntentFilter filter = new IntentFilter(); + filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); + mContext.registerReceiver(/* receiver= */ this, filter); + + synchronized (mLock) { + // Gets initial value. + mIsUnmuteMicDisallowed = isUnmuteMicrophoneDisallowed(); + } + } + + /** Unregisters user restrictions change. */ + void unregister() { + mUserRestrictionsCallback = null; + mContext.unregisterReceiver(/* receiver= */ this); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (UserManager.ACTION_USER_RESTRICTIONS_CHANGED.equals(action)) { + boolean isUnmuteMicDisallowed = isUnmuteMicrophoneDisallowed(); + synchronized (mLock) { + if (isUnmuteMicDisallowed == mIsUnmuteMicDisallowed) { + return; + } + mIsUnmuteMicDisallowed = isUnmuteMicDisallowed; + } + if (mUserRestrictionsCallback != null) { + mUserRestrictionsCallback.onMicrophoneRestrictionChanged(isUnmuteMicDisallowed); + } + } + } +} diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java new file mode 100644 index 000000000000..38e37ec0c316 --- /dev/null +++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.content.Context; +import android.hardware.display.VirtualDisplay; +import android.media.AudioFormat; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.os.RemoteException; + +import java.io.Closeable; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * The class stores an {@link AudioCapture} for audio capturing and an {@link AudioInjection} for + * audio injection. + * + * @hide + */ +@SystemApi +public final class VirtualAudioDevice implements Closeable { + + /** + * Interface to be notified when playback or recording configuration of applications running on + * virtual display was changed. + * + * @hide + */ + @SystemApi + public interface AudioConfigurationChangeCallback { + /** + * Notifies when playback configuration of applications running on virtual display was + * changed. + */ + void onPlaybackConfigChanged(@NonNull List<AudioPlaybackConfiguration> configs); + + /** + * Notifies when recording configuration of applications running on virtual display was + * changed. + */ + void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs); + } + + private final Context mContext; + private final IVirtualDevice mVirtualDevice; + private final VirtualDisplay mVirtualDisplay; + private final AudioConfigurationChangeCallback mCallback; + private final Executor mExecutor; + @Nullable + private VirtualAudioSession mOngoingSession; + + /** + * @hide + */ + public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice, + VirtualDisplay virtualDisplay, Executor executor, + AudioConfigurationChangeCallback callback) { + mContext = context; + mVirtualDevice = virtualDevice; + mVirtualDisplay = virtualDisplay; + mExecutor = executor; + mCallback = callback; + } + + /** + * Begins injecting audio from a remote device into this device. + * + * @return An {@link AudioInjection} containing the injected audio. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @NonNull + public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) { + Objects.requireNonNull(injectionFormat, "injectionFormat must not be null"); + + if (mOngoingSession != null && mOngoingSession.getAudioInjection() != null) { + throw new IllegalStateException("Cannot start an audio injection while a session is " + + "ongoing. Call close() on this device first to end the previous injection."); + } + if (mOngoingSession == null) { + mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor); + } + + try { + mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(), + /* callback= */ mOngoingSession); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return mOngoingSession.startAudioInjection(injectionFormat); + } + + /** + * Begins recording audio emanating from this device. + * + * @return An {@link AudioCapture} containing the recorded audio. + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @NonNull + public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) { + Objects.requireNonNull(captureFormat, "captureFormat must not be null"); + + if (mOngoingSession != null && mOngoingSession.getAudioCapture() != null) { + throw new IllegalStateException("Cannot start an audio capture while a session is " + + "ongoing. Call close() on this device first to end the previous session."); + } + if (mOngoingSession == null) { + mOngoingSession = new VirtualAudioSession(mContext, mCallback, mExecutor); + } + + try { + mVirtualDevice.onAudioSessionStarting(mVirtualDisplay.getDisplay().getDisplayId(), + /* callback= */ mOngoingSession); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return mOngoingSession.startAudioCapture(captureFormat); + } + + /** Returns the {@link AudioCapture} instance. */ + @Nullable + public AudioCapture getAudioCapture() { + return mOngoingSession != null ? mOngoingSession.getAudioCapture() : null; + } + + /** Returns the {@link AudioInjection} instance. */ + @Nullable + public AudioInjection getAudioInjection() { + return mOngoingSession != null ? mOngoingSession.getAudioInjection() : null; + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void close() { + if (mOngoingSession != null) { + mOngoingSession.close(); + mOngoingSession = null; + + try { + mVirtualDevice.onAudioSessionEnded(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/core/java/android/companion/virtual/audio/VirtualAudioSession.java b/core/java/android/companion/virtual/audio/VirtualAudioSession.java new file mode 100644 index 000000000000..bc71bd65e944 --- /dev/null +++ b/core/java/android/companion/virtual/audio/VirtualAudioSession.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual.audio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.companion.virtual.audio.UserRestrictionsDetector.UserRestrictionsCallback; +import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; +import android.content.Context; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecord; +import android.media.AudioRecordingConfiguration; +import android.media.AudioTrack; +import android.media.audiopolicy.AudioMix; +import android.media.audiopolicy.AudioMixingRule; +import android.media.audiopolicy.AudioPolicy; +import android.util.IntArray; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Manages an ongiong audio session in which audio can be captured (recorded) and/or + * injected from a remote device. + * + * @hide + */ +@VisibleForTesting +public final class VirtualAudioSession extends IAudioSessionCallback.Stub implements + UserRestrictionsCallback, Closeable { + private static final String TAG = "VirtualAudioSession"; + + private final Context mContext; + private final UserRestrictionsDetector mUserRestrictionsDetector; + /** The {@link Executor} for sending {@link AudioConfigurationChangeCallback} to the caller */ + private final Executor mExecutor; + @Nullable + private final AudioConfigurationChangeCallback mCallback; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final IntArray mReroutedAppUids = new IntArray(); + @Nullable + @GuardedBy("mLock") + private AudioPolicy mAudioPolicy; + @Nullable + @GuardedBy("mLock") + private AudioFormat mCaptureFormat; + @Nullable + @GuardedBy("mLock") + private AudioFormat mInjectionFormat; + @Nullable + @GuardedBy("mLock") + private AudioCapture mAudioCapture; + @Nullable + @GuardedBy("mLock") + private AudioInjection mAudioInjection; + + @VisibleForTesting + public VirtualAudioSession(Context context, + @Nullable AudioConfigurationChangeCallback callback, @Nullable Executor executor) { + mContext = context; + mUserRestrictionsDetector = new UserRestrictionsDetector(context); + mCallback = callback; + mExecutor = executor != null ? executor : context.getMainExecutor(); + } + + /** + * Begins recording audio emanating from this device. + * + * @return An {@link AudioCapture} containing the recorded audio. + */ + @VisibleForTesting + @NonNull + public AudioCapture startAudioCapture(@NonNull AudioFormat captureFormat) { + Objects.requireNonNull(captureFormat, "captureFormat must not be null"); + + synchronized (mLock) { + if (mAudioCapture != null) { + throw new IllegalStateException( + "Cannot start capture while another capture is ongoing."); + } + + mCaptureFormat = captureFormat; + mAudioCapture = new AudioCapture(); + mAudioCapture.startRecording(); + return mAudioCapture; + } + } + + /** + * Begins injecting audio from a remote device into this device. + * + * @return An {@link AudioInjection} containing the injected audio. + */ + @VisibleForTesting + @NonNull + public AudioInjection startAudioInjection(@NonNull AudioFormat injectionFormat) { + Objects.requireNonNull(injectionFormat, "injectionFormat must not be null"); + mUserRestrictionsDetector.register(/* callback= */ this); + synchronized (mLock) { + if (mAudioInjection != null) { + throw new IllegalStateException( + "Cannot start injection while injection is already ongoing."); + } + + mInjectionFormat = injectionFormat; + mAudioInjection = new AudioInjection(); + mAudioInjection.play(); + mAudioInjection.setSilent(mUserRestrictionsDetector.isUnmuteMicrophoneDisallowed()); + return mAudioInjection; + } + } + + /** @hide */ + @VisibleForTesting + @Nullable + public AudioCapture getAudioCapture() { + synchronized (mLock) { + return mAudioCapture; + } + } + + /** @hide */ + @VisibleForTesting + @Nullable + public AudioInjection getAudioInjection() { + synchronized (mLock) { + return mAudioInjection; + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void onAppsNeedingAudioRoutingChanged(int[] appUids) { + synchronized (mLock) { + if (Arrays.equals(mReroutedAppUids.toArray(), appUids)) { + return; + } + } + + releaseAudioStreams(); + + if (appUids.length == 0) { + return; + } + + createAudioStreams(appUids); + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void close() { + mUserRestrictionsDetector.unregister(); + releaseAudioStreams(); + synchronized (mLock) { + mAudioCapture = null; + mAudioInjection = null; + mCaptureFormat = null; + mInjectionFormat = null; + } + } + + @Override + public void onMicrophoneRestrictionChanged(boolean isUnmuteMicDisallowed) { + synchronized (mLock) { + if (mAudioInjection != null) { + mAudioInjection.setSilent(isUnmuteMicDisallowed); + } + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + private void createAudioStreams(int[] appUids) { + synchronized (mLock) { + if (mCaptureFormat == null && mInjectionFormat == null) { + throw new IllegalStateException( + "At least one of captureFormat and injectionFormat must be specified."); + } + if (mAudioPolicy != null) { + throw new IllegalStateException( + "Cannot create audio streams while the audio policy is registered. Call " + + "releaseAudioStreams() first to unregister the previous audio " + + "policy." + ); + } + + mReroutedAppUids.clear(); + for (int appUid : appUids) { + mReroutedAppUids.add(appUid); + } + + AudioMix audioRecordMix = null; + AudioMix audioTrackMix = null; + AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext); + if (mCaptureFormat != null) { + audioRecordMix = createAudioRecordMix(mCaptureFormat, appUids); + builder.addMix(audioRecordMix); + } + if (mInjectionFormat != null) { + audioTrackMix = createAudioTrackMix(mInjectionFormat, appUids); + builder.addMix(audioTrackMix); + } + mAudioPolicy = builder.build(); + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + if (audioManager.registerAudioPolicy(mAudioPolicy) == AudioManager.ERROR) { + Log.e(TAG, "Failed to register audio policy!"); + } + + AudioRecord audioRecord = + audioRecordMix != null ? mAudioPolicy.createAudioRecordSink(audioRecordMix) + : null; + AudioTrack audioTrack = + audioTrackMix != null ? mAudioPolicy.createAudioTrackSource(audioTrackMix) + : null; + + if (mAudioCapture != null) { + mAudioCapture.setAudioRecord(audioRecord); + } + if (mAudioInjection != null) { + mAudioInjection.setAudioTrack(audioTrack); + } + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + private void releaseAudioStreams() { + synchronized (mLock) { + if (mAudioCapture != null) { + mAudioCapture.setAudioRecord(null); + } + if (mAudioInjection != null) { + mAudioInjection.setAudioTrack(null); + } + mReroutedAppUids.clear(); + if (mAudioPolicy != null) { + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + audioManager.unregisterAudioPolicy(mAudioPolicy); + mAudioPolicy = null; + Log.i(TAG, "AudioPolicy unregistered"); + } + } + } + + @Override + public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { + if (mCallback != null) { + mExecutor.execute(() -> mCallback.onPlaybackConfigChanged(configs)); + } + } + + @Override + public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { + if (mCallback != null) { + mExecutor.execute(() -> mCallback.onRecordingConfigChanged(configs)); + } + } + + /** @hide */ + @VisibleForTesting + public IntArray getReroutedAppUids() { + synchronized (mLock) { + return mReroutedAppUids; + } + } + + private static AudioMix createAudioRecordMix(@NonNull AudioFormat audioFormat, int[] appUids) { + AudioMixingRule.Builder builder = new AudioMixingRule.Builder(); + builder.setTargetMixRole(AudioMixingRule.MIX_ROLE_PLAYERS); + for (int uid : appUids) { + builder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); + } + AudioMixingRule audioMixingRule = builder.allowPrivilegedPlaybackCapture(false).build(); + AudioMix audioMix = + new AudioMix.Builder(audioMixingRule) + .setFormat(audioFormat) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK) + .build(); + return audioMix; + } + + private static AudioMix createAudioTrackMix(@NonNull AudioFormat audioFormat, int[] appUids) { + AudioMixingRule.Builder builder = new AudioMixingRule.Builder(); + builder.setTargetMixRole(AudioMixingRule.MIX_ROLE_INJECTOR); + for (int uid : appUids) { + builder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid); + } + AudioMixingRule audioMixingRule = builder.build(); + AudioMix audioMix = + new AudioMix.Builder(audioMixingRule) + .setFormat(audioFormat) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK) + .build(); + return audioMix; + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 207412511198..62dd627320a6 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -36,6 +36,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UserIdInt; +import android.app.Activity; import android.app.ActivityManager; import android.app.BroadcastOptions; import android.app.GameManager; @@ -45,6 +46,8 @@ import android.app.VrManager; import android.app.ambientcontext.AmbientContextManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -85,6 +88,7 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import android.view.textclassifier.TextClassificationManager; import android.window.WindowContext; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.IPlatformCompat; import com.android.internal.compat.IPlatformCompatNative; @@ -111,6 +115,19 @@ import java.util.function.Consumer; * broadcasting and receiving intents, etc. */ public abstract class Context { + /** + * After {@link Build.VERSION_CODES#TIRAMISU}, + * {@link #registerComponentCallbacks(ComponentCallbacks)} will add a {@link ComponentCallbacks} + * to {@link Activity} or {@link ContextWrapper#getBaseContext()} instead of always adding to + * {@link #getApplicationContext()}. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + @VisibleForTesting + public static final long OVERRIDABLE_COMPONENT_CALLBACKS = 193247900L; + /** @hide */ @IntDef(flag = true, prefix = { "MODE_" }, value = { MODE_PRIVATE, diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 98ced6d7ed5a..9adf17367039 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -25,8 +25,6 @@ import android.annotation.UiContext; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -74,16 +72,6 @@ public class ContextWrapper extends Context { Context mBase; /** - * After {@link Build.VERSION_CODES#TIRAMISU}, - * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to - * {@link #getBaseContext()} instead of {@link #getApplicationContext()}. - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) - @VisibleForTesting - static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L; - - /** * A list to store {@link ComponentCallbacks} which * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before * {@link #attachBaseContext(Context)}. @@ -1355,7 +1343,7 @@ public class ContextWrapper extends Context { public void registerComponentCallbacks(ComponentCallbacks callback) { if (mBase != null) { mBase.registerComponentCallbacks(callback); - } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) { + } else if (!CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) { super.registerComponentCallbacks(callback); synchronized (mLock) { // Also register ComponentCallbacks to ContextWrapper, so we can find the correct @@ -1397,7 +1385,7 @@ public class ContextWrapper extends Context { mCallbacksRegisteredToSuper.remove(callback); } else if (mBase != null) { mBase.unregisterComponentCallbacks(callback); - } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) { + } else if (CompatChanges.isChangeEnabled(OVERRIDABLE_COMPONENT_CALLBACKS)) { // Throw exception for Application that is targeting S-v2+ throw new IllegalStateException("ComponentCallbacks must be unregistered after " + "this ContextWrapper is attached to a base Context."); diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index eefa1d3279e3..623f38e3f48b 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1214,17 +1214,6 @@ public final class DisplayManager { } /** - * Returns whether the specified display supports DISPLAY_DECORATION. - * - * @param displayId The display to query support. - * - * @hide - */ - public boolean getDisplayDecorationSupport(int displayId) { - return mGlobal.getDisplayDecorationSupport(displayId); - } - - /** * Returns the user preference for "Match content frame rate". * <p> * Never: Even if the app requests it, the device will never try to match its output to the diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index af8ec279ac30..7e7a64803dd9 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -32,6 +32,7 @@ import android.content.res.Resources; import android.graphics.ColorSpace; import android.graphics.Point; import android.hardware.display.DisplayManager.DisplayListener; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.media.projection.IMediaProjection; import android.media.projection.MediaProjection; import android.os.Handler; @@ -812,13 +813,13 @@ public final class DisplayManagerGlobal { } /** - * Report whether the display supports DISPLAY_DECORATION. + * Report whether/how the display supports DISPLAY_DECORATION. * * @param displayId The display whose support is being queried. * * @hide */ - public boolean getDisplayDecorationSupport(int displayId) { + public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) { try { return mDm.getDisplayDecorationSupport(displayId); } catch (RemoteException ex) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index b3af52b19063..ddd18f4502e7 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -22,6 +22,7 @@ import android.graphics.Point; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; @@ -183,5 +184,5 @@ interface IDisplayManager { int getRefreshRateSwitchingType(); // Query for DISPLAY_DECORATION support. - boolean getDisplayDecorationSupport(int displayId); + DisplayDecorationSupport getDisplayDecorationSupport(int displayId); } diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java index d875156f5dc7..26d74df6f2ef 100644 --- a/core/java/android/hardware/input/VirtualKeyEvent.java +++ b/core/java/android/hardware/input/VirtualKeyEvent.java @@ -114,9 +114,56 @@ public final class VirtualKeyEvent implements Parcelable { } /** - * Sets the Android key code of the event. The set of allowed characters include digits 0-9, - * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12, - * and meta keys (caps lock, shift, etc.). + * Sets the Android key code of the event. The set of allowed keys include digits + * {@link android.view.KeyEvent#KEYCODE_0} through + * {@link android.view.KeyEvent#KEYCODE_9}, characters + * {@link android.view.KeyEvent#KEYCODE_A} through + * {@link android.view.KeyEvent#KEYCODE_Z}, function keys + * {@link android.view.KeyEvent#KEYCODE_F1} through + * {@link android.view.KeyEvent#KEYCODE_F12}, numpad keys + * {@link android.view.KeyEvent#KEYCODE_NUMPAD_0} through + * {@link android.view.KeyEvent#KEYCODE_NUMPAD_RIGHT_PAREN}, + * and these additional keys: + * {@link android.view.KeyEvent#KEYCODE_GRAVE} + * {@link android.view.KeyEvent#KEYCODE_MINUS} + * {@link android.view.KeyEvent#KEYCODE_EQUALS} + * {@link android.view.KeyEvent#KEYCODE_LEFT_BRACKET} + * {@link android.view.KeyEvent#KEYCODE_RIGHT_BRACKET} + * {@link android.view.KeyEvent#KEYCODE_BACKSLASH} + * {@link android.view.KeyEvent#KEYCODE_SEMICOLON} + * {@link android.view.KeyEvent#KEYCODE_APOSTROPHE} + * {@link android.view.KeyEvent#KEYCODE_COMMA} + * {@link android.view.KeyEvent#KEYCODE_PERIOD} + * {@link android.view.KeyEvent#KEYCODE_SLASH} + * {@link android.view.KeyEvent#KEYCODE_ALT_LEFT} + * {@link android.view.KeyEvent#KEYCODE_ALT_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_CTRL_LEFT} + * {@link android.view.KeyEvent#KEYCODE_CTRL_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_SHIFT_LEFT} + * {@link android.view.KeyEvent#KEYCODE_SHIFT_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_META_LEFT} + * {@link android.view.KeyEvent#KEYCODE_META_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_CAPS_LOCK} + * {@link android.view.KeyEvent#KEYCODE_SCROLL_LOCK} + * {@link android.view.KeyEvent#KEYCODE_NUM_LOCK} + * {@link android.view.KeyEvent#KEYCODE_ENTER} + * {@link android.view.KeyEvent#KEYCODE_TAB} + * {@link android.view.KeyEvent#KEYCODE_SPACE} + * {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN} + * {@link android.view.KeyEvent#KEYCODE_DPAD_UP} + * {@link android.view.KeyEvent#KEYCODE_DPAD_LEFT} + * {@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT} + * {@link android.view.KeyEvent#KEYCODE_MOVE_END} + * {@link android.view.KeyEvent#KEYCODE_MOVE_HOME} + * {@link android.view.KeyEvent#KEYCODE_PAGE_DOWN} + * {@link android.view.KeyEvent#KEYCODE_PAGE_UP} + * {@link android.view.KeyEvent#KEYCODE_DEL} + * {@link android.view.KeyEvent#KEYCODE_FORWARD_DEL} + * {@link android.view.KeyEvent#KEYCODE_INSERT} + * {@link android.view.KeyEvent#KEYCODE_ESCAPE} + * {@link android.view.KeyEvent#KEYCODE_BREAK} + * {@link android.view.KeyEvent#KEYCODE_BACK} + * {@link android.view.KeyEvent#KEYCODE_FORWARD} * * @return this builder, to allow for chaining of calls */ diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index eccbb403b306..75356d1ce994 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -41,7 +41,9 @@ import com.android.internal.os.SomeArgs; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodSession; -class IInputMethodSessionWrapper extends IInputMethodSession.Stub +/** @hide */ +// TODO(b/215636776): move IInputMethodSessionWrapper to proper package +public class IInputMethodSessionWrapper extends IInputMethodSession.Stub implements HandlerCaller.Callback { private static final String TAG = "InputMethodWrapper"; diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java index 83b053aaa034..499634ae5bec 100644 --- a/core/java/android/inputmethodservice/InkWindow.java +++ b/core/java/android/inputmethodservice/InkWindow.java @@ -102,11 +102,4 @@ final class InkWindow extends PhoneWindow { lp.token = token; setAttributes(lp); } - - /** - * Returns {@code true} if Window was created and added to WM. - */ - boolean isInitialized() { - return mIsViewAdded; - } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index b6e1b1f1fa3f..adf2759a8cd8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -578,6 +578,7 @@ public class InputMethodService extends AbstractInputMethodService { private boolean mImeSurfaceScheduledForRemoval; private ImsConfigurationTracker mConfigTracker = new ImsConfigurationTracker(); private boolean mDestroyed; + private boolean mOnPreparedStylusHwCalled; /** Stylus handwriting Ink window. */ private InkWindow mInkWindow; @@ -919,9 +920,10 @@ public class InputMethodService extends AbstractInputMethodService { Log.d(TAG, "Input should have started before starting Stylus handwriting."); return; } - if (!mInkWindow.isInitialized()) { + if (!mOnPreparedStylusHwCalled) { // prepare hasn't been called by Stylus HOVER. onPrepareStylusHandwriting(); + mOnPreparedStylusHwCalled = true; } if (onStartStylusHandwriting()) { mPrivOps.onStylusHandwritingReady(requestId); @@ -976,6 +978,7 @@ public class InputMethodService extends AbstractInputMethodService { public void initInkWindow() { mInkWindow.initOnly(); onPrepareStylusHandwriting(); + mOnPreparedStylusHwCalled = true; } /** @@ -2354,7 +2357,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * Called to prepare stylus handwriting. - * The system calls this before the first {@link #onStartStylusHandwriting} request. + * The system calls this before the {@link #onStartStylusHandwriting} request. * * <p>Note: The system tries to call this as early as possible, when it detects that * handwriting stylus input is imminent. However, that a subsequent call to @@ -2438,6 +2441,7 @@ public class InputMethodService extends AbstractInputMethodService { mInkWindow.hide(false /* remove */); mPrivOps.finishStylusHandwriting(requestId); + mOnPreparedStylusHwCalled = false; onFinishStylusHandwriting(); } diff --git a/core/java/android/inputmethodservice/InputMethodServiceInternal.java b/core/java/android/inputmethodservice/InputMethodServiceInternal.java index 7cd4ff61b2c8..09dbb27359b0 100644 --- a/core/java/android/inputmethodservice/InputMethodServiceInternal.java +++ b/core/java/android/inputmethodservice/InputMethodServiceInternal.java @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.view.inputmethod.InputConnection; @@ -31,8 +32,11 @@ import java.io.PrintWriter; * framework classes for internal use. * * <p>CAVEATS: {@link AbstractInputMethodService} does not support all the methods here.</p> + * + * @hide */ -interface InputMethodServiceInternal { +// TODO(b/215636776): move InputMethodServiceInternal to proper package +public interface InputMethodServiceInternal { /** * @return {@link Context} associated with the service. */ @@ -70,7 +74,8 @@ interface InputMethodServiceInternal { * closed for you after you return. * @param args additional arguments to the dump request. */ - default void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + default void dump(@SuppressLint("UseParcelFileDescriptor") @NonNull FileDescriptor fd, + @NonNull PrintWriter fout, @NonNull String[] args) { } /** @@ -81,6 +86,6 @@ interface InputMethodServiceInternal { * @param where {@code where} parameter to be passed. * @param icProto {@code icProto} parameter to be passed. */ - default void triggerServiceDump(String where, @Nullable byte[] icProto) { + default void triggerServiceDump(@NonNull String where, @Nullable byte[] icProto) { } } diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index 9ef2579d04e2..6b7815d0f732 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -53,8 +53,11 @@ import java.util.concurrent.CompletableFuture; * * <p>See also {@link IInputContext} for the actual {@link android.os.Binder} IPC protocols under * the hood.</p> + * + * @hide */ -final class RemoteInputConnection implements InputConnection { +// TODO(b/215636776): move RemoteInputConnection to proper package +public final class RemoteInputConnection implements InputConnection { private static final String TAG = "RemoteInputConnection"; private static final int MAX_WAIT_TIME_MILLIS = 2000; @@ -95,7 +98,7 @@ final class RemoteInputConnection implements InputConnection { @NonNull private final CancellationGroup mCancellationGroup; - RemoteInputConnection( + public RemoteInputConnection( @NonNull WeakReference<InputMethodServiceInternal> inputMethodService, IInputContext inputContext, @NonNull CancellationGroup cancellationGroup) { mImsInternal = new InputMethodServiceInternalHolder(inputMethodService); @@ -108,7 +111,7 @@ final class RemoteInputConnection implements InputConnection { return mInvoker.isSameConnection(inputContext); } - RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) { + public RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) { mImsInternal = original.mImsInternal; mInvoker = original.mInvoker.cloneWithSessionId(sessionId); mCancellationGroup = original.mCancellationGroup; diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java index ec752fdbf45f..0fd3e034291b 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -547,7 +547,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { if (profile.excludeLocalRoutes && !profile.isBypassable) { Log.w(TAG, "ExcludeLocalRoutes should only be set in the bypassable VPN"); } - builder.setExcludeLocalRoutes(profile.excludeLocalRoutes && profile.isBypassable); + + builder.setLocalRoutesExcluded(profile.excludeLocalRoutes && profile.isBypassable); builder.setRequiresInternetValidation(profile.requiresInternetValidation); return builder.build(); @@ -1104,7 +1105,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { */ @NonNull @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) - public Builder setExcludeLocalRoutes(boolean excludeLocalRoutes) { + public Builder setLocalRoutesExcluded(boolean excludeLocalRoutes) { mExcludeLocalRoutes = excludeLocalRoutes; return this; } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index d8f098eb880b..df12d2c61ce0 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -167,6 +167,8 @@ public class NetworkPolicyManager { public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave"; /** @hide */ public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java index 8bd1c8d07017..c0fb4cf4f3dd 100644 --- a/core/java/android/net/PlatformVpnProfile.java +++ b/core/java/android/net/PlatformVpnProfile.java @@ -83,7 +83,7 @@ public abstract class PlatformVpnProfile { /** * Returns whether the local traffic is exempted from the VPN. */ - public final boolean getExcludeLocalRoutes() { + public final boolean areLocalRoutesExcluded() { return mExcludeLocalRoutes; } diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 5aad997af8c1..779d931245c8 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -24,6 +24,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.app.Activity; @@ -52,7 +53,7 @@ import java.util.List; * app (unlike VpnService). * * <p>VPN apps using supported protocols should preferentially use this API over the {@link - * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user + * VpnService} API for ease-of-development and reduced maintenance burden. This also give the user * the guarantee that VPN network traffic is not subjected to on-device packet interception. * * @see Ikev2VpnProfile @@ -97,130 +98,173 @@ public class VpnManager { public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; /** - * Action sent in the intent when an error occurred. + * Action sent in {@link android.content.Intent}s to VpnManager clients when an event occurred. * - * @hide + * This action will have a category of either {@link #CATEGORY_EVENT_IKE_ERROR}, + * {@link #CATEGORY_EVENT_NETWORK_ERROR}, or {@link #CATEGORY_EVENT_DEACTIVATED_BY_USER}, + * that the app can use to filter events it's interested in reacting to. + * + * It will also contain the following extras : + * <ul> + * <li>{@link #EXTRA_SESSION_KEY}, a {@code String} for the session key, as returned by + * {@link #startProvisionedVpnProfileSession}. + * <li>{@link #EXTRA_TIMESTAMP}, a long for the timestamp at which the error occurred, + * in milliseconds since the epoch, as returned by + * {@link java.lang.System#currentTimeMillis}. + * <li>{@link #EXTRA_UNDERLYING_NETWORK}, a {@link Network} containing the underlying + * network at the time the error occurred, or null if none. Note that this network + * may have disconnected already. + * <li>{@link #EXTRA_UNDERLYING_NETWORK_CAPABILITIES}, a {@link NetworkCapabilities} for + * the underlying network at the time the error occurred. + * <li>{@link #EXTRA_UNDERLYING_LINK_PROPERTIES}, a {@link LinkProperties} for the underlying + * network at the time the error occurred. + * </ul> + * When this event is an error, either {@link #CATEGORY_EVENT_IKE_ERROR} or + * {@link #CATEGORY_EVENT_NETWORK_ERROR}, the following extras will be populated : + * <ul> + * <li>{@link #EXTRA_ERROR_CLASS}, an {@code int} for the class of error, either + * {@link #ERROR_CLASS_RECOVERABLE} or {@link #ERROR_CLASS_NOT_RECOVERABLE}. + * <li>{@link #EXTRA_ERROR_CODE}, an {@code int} error code specific to the error. See + * {@link #EXTRA_ERROR_CODE} for details. + * </ul> */ - public static final String ACTION_VPN_MANAGER_ERROR = "android.net.action.VPN_MANAGER_ERROR"; + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String ACTION_VPN_MANAGER_EVENT = "android.net.action.VPN_MANAGER_EVENT"; /** - * An IKE protocol error. Codes are the codes from IkeProtocolException, RFC 7296. + * An IKE protocol error occurred. * - * @hide + * Codes (in {@link #EXTRA_ERROR_CODE}) are the codes from + * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}, as defined by IANA in + * "IKEv2 Notify Message Types - Error Types". */ - public static final String CATEGORY_ERROR_IKE = "android.net.category.ERROR_IKE"; + @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EVENT_IKE_ERROR = "android.net.category.EVENT_IKE_ERROR"; /** - * User deactivated the VPN, either by turning it off or selecting a different VPN provider. - * The error code is always 0. + * A network error occurred. * - * @hide + * Error codes (in {@link #EXTRA_ERROR_CODE}) are ERROR_CODE_NETWORK_*. */ - public static final String CATEGORY_ERROR_USER_DEACTIVATED = - "android.net.category.ERROR_USER_DEACTIVATED"; + @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EVENT_NETWORK_ERROR = + "android.net.category.EVENT_NETWORK_ERROR"; /** - * Network error. Error codes are ERROR_CODE_NETWORK_*. + * The user deactivated the VPN. * - * @hide + * This can happen either when the user turns the VPN off explicitly, or when they select + * a different VPN provider. */ - public static final String CATEGORY_ERROR_NETWORK = "android.net.category.ERROR_NETWORK"; + @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_EVENT_DEACTIVATED_BY_USER = + "android.net.category.EVENT_DEACTIVATED_BY_USER"; /** - * The key of the session that experienced this error, as returned by - * startProvisionedVpnProfileSession. + * The key of the session that experienced this event, as a {@code String}. * - * @hide + * This is the same key that was returned by {@link #startProvisionedVpnProfileSession}. */ public static final String EXTRA_SESSION_KEY = "android.net.extra.SESSION_KEY"; /** - * Extra for the Network object that was the underlying network at the time of the failure, or - * null if none. + * The network that was underlying the VPN when the event occurred, as a {@link Network}. * - * @hide + * This extra will be null if there was no underlying network at the time of the event. */ public static final String EXTRA_UNDERLYING_NETWORK = "android.net.extra.UNDERLYING_NETWORK"; /** - * The NetworkCapabilities of the underlying network. + * The {@link NetworkCapabilities} of the underlying network when the event occurred. * - * @hide + * This extra will be null if there was no underlying network at the time of the event. */ public static final String EXTRA_UNDERLYING_NETWORK_CAPABILITIES = "android.net.extra.UNDERLYING_NETWORK_CAPABILITIES"; /** - * The LinkProperties of the underlying network. + * The {@link LinkProperties} of the underlying network when the event occurred. * - * @hide + * This extra will be null if there was no underlying network at the time of the event. */ public static final String EXTRA_UNDERLYING_LINK_PROPERTIES = "android.net.extra.UNDERLYING_LINK_PROPERTIES"; /** - * A long timestamp with SystemClock.elapsedRealtime base for when the event happened. + * A {@code long} timestamp containing the time at which the event occurred. * - * @hide + * This is a number of milliseconds since the epoch, suitable to be compared with + * {@link java.lang.System#currentTimeMillis}. */ public static final String EXTRA_TIMESTAMP = "android.net.extra.TIMESTAMP"; /** - * Extra for the error type. This is ERROR_NOT_RECOVERABLE or ERROR_RECOVERABLE. + * Extra for the error class, as an {@code int}. * - * @hide + * This is always either {@link #ERROR_CLASS_NOT_RECOVERABLE} or + * {@link #ERROR_CLASS_RECOVERABLE}. This extra is only populated for error categories. */ - public static final String EXTRA_ERROR_TYPE = "android.net.extra.ERROR_TYPE"; + public static final String EXTRA_ERROR_CLASS = "android.net.extra.ERROR_CLASS"; /** - * Extra for the error code. The value will be 0 for CATEGORY_ERROR_USER_DEACTIVATED, one of - * ERROR_CODE_NETWORK_* for ERROR_CATEGORY_NETWORK or one of values defined in - * IkeProtocolException#ErrorType for CATEGORY_ERROR_IKE. + * Extra for an error code, as an {@code int}. * - * @hide + * <ul> + * <li>For {@link #CATEGORY_EVENT_NETWORK_ERROR}, this is one of the + * {@code ERROR_CODE_NETWORK_*} constants. + * <li>For {@link #CATEGORY_EVENT_IKE_ERROR}, this is one of values defined in + * {@link android.net.ipsec.ike.exceptions.IkeProtocolException}.ERROR_TYPE_*. + * </ul> + * For non-error categories, this extra is not populated. */ public static final String EXTRA_ERROR_CODE = "android.net.extra.ERROR_CODE"; /** - * This error is fatal, e.g. the VPN was disabled or configuration error. The stack will not - * retry connection. + * {@link #EXTRA_ERROR_CLASS} coding for a non-recoverable error. * - * @hide + * This error is fatal, e.g. configuration error. The stack will not retry connection. */ - public static final int ERROR_NOT_RECOVERABLE = 1; + public static final int ERROR_CLASS_NOT_RECOVERABLE = 1; /** - * The stack experienced an error but will retry with exponential backoff, e.g. network timeout. + * {@link #EXTRA_ERROR_CLASS} coding for a recoverable error. * - * @hide + * The stack experienced an error but will retry with exponential backoff, e.g. network timeout. */ - public static final int ERROR_RECOVERABLE = 2; + public static final int ERROR_CLASS_RECOVERABLE = 2; /** - * An error code to indicate that there was an UnknownHostException. + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} to indicate that the + * network host isn't known. * - * @hide + * This happens when domain name resolution could not resolve an IP address for the + * specified host. {@see java.net.UnknownHostException} */ public static final int ERROR_CODE_NETWORK_UNKNOWN_HOST = 0; /** - * An error code to indicate that there is a SocketTimeoutException. + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating a timeout. * - * @hide + * For Ikev2 VPNs, this happens typically after a retransmission failure. + * {@see android.net.ipsec.ike.exceptions.IkeTimeoutException} */ - public static final int ERROR_CODE_NETWORK_TIMEOUT = 1; + public static final int ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT = 1; /** - * An error code to indicate the connection was reset. (e.g. SocketException) + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating that + * network connectivity was lost. * - * @hide + * The most common reason for this error is that the underlying network was disconnected, + * {@see android.net.ipsec.ike.exceptions.IkeNetworkLostException}. */ - public static final int ERROR_CODE_NETWORK_RESET = 2; + public static final int ERROR_CODE_NETWORK_LOST = 2; /** - * An error code to indicate that there is an IOException. + * An {@link #EXTRA_ERROR_CODE} for {@link #CATEGORY_EVENT_NETWORK_ERROR} indicating an + * input/output error. * - * @hide + * This code happens when reading or writing to sockets on the underlying networks was + * terminated by an I/O error. {@see IOException}. */ public static final int ERROR_CODE_NETWORK_IO = 3; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index f1d4ba0029bd..de1dc8091b2a 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -52,10 +52,9 @@ import android.util.proto.ProtoOutputStream; import android.view.Display; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.BatterySipper; -import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryUsageStatsProvider; -import com.android.internal.os.PowerCalculator; + +import com.google.android.collect.Lists; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -70,6 +69,7 @@ import java.util.Formatter; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -3539,6 +3539,44 @@ public abstract class BatteryStats implements Parcelable { } } + /** + * Converts charge in mAh to string. + */ + public static String formatCharge(double power) { + return formatValue(power); + } + + /** + * Converts double to string, limiting small values to 3 significant figures. + */ + private static String formatValue(double value) { + if (value == 0) return "0"; + + final String format; + if (value < .00001) { + format = "%.8f"; + } else if (value < .0001) { + format = "%.7f"; + } else if (value < .001) { + format = "%.6f"; + } else if (value < .01) { + format = "%.5f"; + } else if (value < .1) { + format = "%.4f"; + } else if (value < 1) { + format = "%.3f"; + } else if (value < 10) { + format = "%.2f"; + } else if (value < 100) { + format = "%.1f"; + } else { + format = "%.0f"; + } + + // Use English locale because this is never used in UI (only in checkin and dump). + return String.format(Locale.ENGLISH, format, value); + } + private static long roundUsToMs(long timeUs) { return (timeUs + 500) / 1000; } @@ -4025,7 +4063,7 @@ public abstract class BatteryStats implements Parcelable { sb.append(" "); sb.append(controllerName); sb.append(" Battery drain: ").append( - PowerCalculator.formatCharge(powerDrainMaMs / MILLISECONDS_IN_HOUR)); + formatCharge(powerDrainMaMs / MILLISECONDS_IN_HOUR)); sb.append("mAh"); pw.println(sb.toString()); } @@ -4424,10 +4462,10 @@ public abstract class BatteryStats implements Parcelable { final BatteryUsageStats stats = getBatteryUsageStats(context); dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA, - PowerCalculator.formatCharge(stats.getBatteryCapacity()), - PowerCalculator.formatCharge(stats.getConsumedPower()), - PowerCalculator.formatCharge(stats.getDischargedPowerRange().getLower()), - PowerCalculator.formatCharge(stats.getDischargedPowerRange().getUpper())); + formatCharge(stats.getBatteryCapacity()), + formatCharge(stats.getConsumedPower()), + formatCharge(stats.getDischargedPowerRange().getLower()), + formatCharge(stats.getDischargedPowerRange().getUpper())); final BatteryConsumer deviceConsumer = stats.getAggregateBatteryConsumer( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); for (@BatteryConsumer.PowerComponent int powerComponent = 0; @@ -4437,7 +4475,7 @@ public abstract class BatteryStats implements Parcelable { label = "???"; } dumpLine(pw, 0 /* uid */, category, POWER_USE_ITEM_DATA, label, - PowerCalculator.formatCharge(deviceConsumer.getConsumedPower(powerComponent)), + formatCharge(deviceConsumer.getConsumedPower(powerComponent)), shouldHidePowerComponent(powerComponent) ? 1 : 0, "0", "0"); } @@ -4447,11 +4485,10 @@ public abstract class BatteryStats implements Parcelable { for (int i = 0; i < uidBatteryConsumers.size(); i++) { UidBatteryConsumer consumer = uidBatteryConsumers.get(i); dumpLine(pw, consumer.getUid(), category, POWER_USE_ITEM_DATA, "uid", - PowerCalculator.formatCharge(consumer.getConsumedPower()), + formatCharge(consumer.getConsumedPower()), proportionalAttributionCalculator.isSystemBatteryConsumer(consumer) ? 1 : 0, - PowerCalculator.formatCharge( - consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)), - PowerCalculator.formatCharge( + formatCharge(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)), + formatCharge( proportionalAttributionCalculator.getProportionalPowerMah(consumer))); } @@ -4885,11 +4922,11 @@ public abstract class BatteryStats implements Parcelable { } private void printmAh(PrintWriter printer, double power) { - printer.print(PowerCalculator.formatCharge(power)); + printer.print(formatCharge(power)); } private void printmAh(StringBuilder sb, double power) { - sb.append(PowerCalculator.formatCharge(power)); + sb.append(formatCharge(power)); } /** @@ -4936,7 +4973,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Estimated battery capacity: "); - sb.append(PowerCalculator.formatCharge(estimatedBatteryCapacity)); + sb.append(formatCharge(estimatedBatteryCapacity)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -4946,7 +4983,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Last learned battery capacity: "); - sb.append(PowerCalculator.formatCharge(lastLearnedBatteryCapacity / 1000)); + sb.append(formatCharge(lastLearnedBatteryCapacity / 1000)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -4955,7 +4992,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Min learned battery capacity: "); - sb.append(PowerCalculator.formatCharge(minLearnedBatteryCapacity / 1000)); + sb.append(formatCharge(minLearnedBatteryCapacity / 1000)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -4964,7 +5001,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Max learned battery capacity: "); - sb.append(PowerCalculator.formatCharge(maxLearnedBatteryCapacity / 1000)); + sb.append(formatCharge(maxLearnedBatteryCapacity / 1000)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5028,7 +5065,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Discharge: "); - sb.append(PowerCalculator.formatCharge(dischargeCount / 1000.0)); + sb.append(formatCharge(dischargeCount / 1000.0)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5038,7 +5075,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Screen off discharge: "); - sb.append(PowerCalculator.formatCharge(dischargeScreenOffCount / 1000.0)); + sb.append(formatCharge(dischargeScreenOffCount / 1000.0)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5048,7 +5085,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Screen doze discharge: "); - sb.append(PowerCalculator.formatCharge(dischargeScreenDozeCount / 1000.0)); + sb.append(formatCharge(dischargeScreenDozeCount / 1000.0)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5058,7 +5095,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Screen on discharge: "); - sb.append(PowerCalculator.formatCharge(dischargeScreenOnCount / 1000.0)); + sb.append(formatCharge(dischargeScreenOnCount / 1000.0)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5068,7 +5105,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Device light doze discharge: "); - sb.append(PowerCalculator.formatCharge(dischargeLightDozeCount / 1000.0)); + sb.append(formatCharge(dischargeLightDozeCount / 1000.0)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5078,7 +5115,7 @@ public abstract class BatteryStats implements Parcelable { sb.setLength(0); sb.append(prefix); sb.append(" Device deep doze discharge: "); - sb.append(PowerCalculator.formatCharge(dischargeDeepDozeCount / 1000.0)); + sb.append(formatCharge(dischargeDeepDozeCount / 1000.0)); sb.append(" mAh"); pw.println(sb.toString()); } @@ -5212,7 +5249,7 @@ public abstract class BatteryStats implements Parcelable { for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); - final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks + final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { final Uid.Wakelock wl = wakelocks.valueAt(iw); @@ -5621,34 +5658,38 @@ public abstract class BatteryStats implements Parcelable { .build()); stats.dump(pw, prefix); - final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly); - helper.create(this); - helper.refreshStats(which, UserHandle.USER_ALL); - - final List<BatterySipper> sippers = helper.getMobilemsppList(); - if (sippers != null && sippers.size() > 0) { - pw.print(prefix); pw.println(" Per-app mobile ms per packet:"); + List<UidMobileRadioStats> uidMobileRadioStats = + getUidMobileRadioStats(stats.getUidBatteryConsumers()); + if (uidMobileRadioStats.size() > 0) { + pw.print(prefix); + pw.println(" Per-app mobile ms per packet:"); long totalTime = 0; - for (int i=0; i<sippers.size(); i++) { - final BatterySipper bs = sippers.get(i); + for (int i = 0; i < uidMobileRadioStats.size(); i++) { + final UidMobileRadioStats mrs = uidMobileRadioStats.get(i); sb.setLength(0); - sb.append(prefix); sb.append(" Uid "); - UserHandle.formatUid(sb, bs.uidObj.getUid()); + sb.append(prefix); + sb.append(" Uid "); + UserHandle.formatUid(sb, mrs.uid); sb.append(": "); - sb.append(PowerCalculator.formatCharge(bs.mobilemspp)); - sb.append(" ("); sb.append(bs.mobileRxPackets+bs.mobileTxPackets); - sb.append(" packets over "); formatTimeMsNoSpace(sb, bs.mobileActive); - sb.append(") "); sb.append(bs.mobileActiveCount); sb.append("x"); - pw.println(sb.toString()); - totalTime += bs.mobileActive; + sb.append(formatValue(mrs.millisecondsPerPacket)); + sb.append(" ("); + sb.append(mrs.rxPackets + mrs.txPackets); + sb.append(" packets over "); + formatTimeMsNoSpace(sb, mrs.radioActiveMs); + sb.append(") "); + sb.append(mrs.radioActiveCount); + sb.append("x"); + pw.println(sb); + totalTime += mrs.radioActiveMs; } sb.setLength(0); sb.append(prefix); sb.append(" TOTAL TIME: "); formatTimeMs(sb, totalTime); - sb.append("("); sb.append(formatRatioLocked(totalTime, whichBatteryRealtime)); + sb.append("("); + sb.append(formatRatioLocked(totalTime, whichBatteryRealtime)); sb.append(")"); - pw.println(sb.toString()); + pw.println(sb); pw.println(); } @@ -5668,13 +5709,13 @@ public abstract class BatteryStats implements Parcelable { }; if (reqUid < 0) { - final Map<String, ? extends BatteryStats.Timer> kernelWakelocks + final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats(); if (kernelWakelocks.size() > 0) { final ArrayList<TimerEntry> ktimers = new ArrayList<>(); - for (Map.Entry<String, ? extends BatteryStats.Timer> ent + for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) { - final BatteryStats.Timer timer = ent.getValue(); + final Timer timer = ent.getValue(); final long totalTimeMillis = computeWakeLock(timer, rawRealtime, which); if (totalTimeMillis > 0) { ktimers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis)); @@ -5859,8 +5900,7 @@ public abstract class BatteryStats implements Parcelable { packets = 1; } sb.append(" @ "); - sb.append(PowerCalculator.formatCharge( - uidMobileActiveTime / 1000 / (double) packets)); + sb.append(formatCharge(uidMobileActiveTime / 1000 / (double) packets)); sb.append(" mspp"); pw.println(sb.toString()); } @@ -6068,7 +6108,7 @@ public abstract class BatteryStats implements Parcelable { } } - final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks + final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats(); long totalFullWakelock = 0, totalPartialWakelock = 0, totalWindowWakelock = 0; long totalDrawWakelock = 0; @@ -6296,7 +6336,7 @@ public abstract class BatteryStats implements Parcelable { uidActivity |= printTimer(pw, sb, u.getAudioTurnedOnTimer(), rawRealtime, which, prefix, "Audio"); - final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats(); + final SparseArray<? extends Uid.Sensor> sensors = u.getSensorStats(); final int NSE = sensors.size(); for (int ise=0; ise<NSE; ise++) { final Uid.Sensor se = sensors.valueAt(ise); @@ -6439,7 +6479,7 @@ public abstract class BatteryStats implements Parcelable { } } - final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats + final ArrayMap<String, ? extends Uid.Proc> processStats = u.getProcessStats(); for (int ipr=processStats.size()-1; ipr>=0; ipr--) { final Uid.Proc ps = processStats.valueAt(ipr); @@ -6513,7 +6553,7 @@ public abstract class BatteryStats implements Parcelable { } } - final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats + final ArrayMap<String, ? extends Uid.Pkg> packageStats = u.getPackageStats(); for (int ipkg=packageStats.size()-1; ipkg>=0; ipkg--) { pw.print(prefix); pw.print(" Apk "); pw.print(packageStats.keyAt(ipkg)); @@ -6530,7 +6570,7 @@ public abstract class BatteryStats implements Parcelable { } final ArrayMap<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats(); for (int isvc=serviceStats.size()-1; isvc>=0; isvc--) { - final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc); + final Uid.Pkg.Serv ss = serviceStats.valueAt(isvc); final long startTime = ss.getStartTime(batteryUptime, which); final int starts = ss.getStarts(which); final int launches = ss.getLaunches(which); @@ -8551,7 +8591,10 @@ public abstract class BatteryStats implements Parcelable { proto.end(sToken); } - private static boolean checkWifiOnly(Context context) { + /** + * Returns true if the device does not have data-capable telephony. + */ + public static boolean checkWifiOnly(Context context) { final TelephonyManager tm = context.getSystemService(TelephonyManager.class); if (tm == null) { return false; @@ -8652,4 +8695,57 @@ public abstract class BatteryStats implements Parcelable { return false; } } + + private static class UidMobileRadioStats { + public final int uid; + public final long rxPackets; + public final long txPackets; + public final long radioActiveMs; + public final int radioActiveCount; + public final double millisecondsPerPacket; + + private UidMobileRadioStats(int uid, long rxPackets, long txPackets, long radioActiveMs, + int radioActiveCount, double millisecondsPerPacket) { + this.uid = uid; + this.txPackets = txPackets; + this.rxPackets = rxPackets; + this.radioActiveMs = radioActiveMs; + this.radioActiveCount = radioActiveCount; + this.millisecondsPerPacket = millisecondsPerPacket; + } + } + + private List<UidMobileRadioStats> getUidMobileRadioStats( + List<UidBatteryConsumer> uidBatteryConsumers) { + final SparseArray<? extends Uid> uidStats = getUidStats(); + List<UidMobileRadioStats> uidMobileRadioStats = Lists.newArrayList(); + for (int i = 0; i < uidBatteryConsumers.size(); i++) { + final UidBatteryConsumer consumer = uidBatteryConsumers.get(i); + if (consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) == 0) { + continue; + } + + final int uid = consumer.getUid(); + final Uid u = uidStats.get(uid); + final long rxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_MOBILE_RX_DATA, STATS_SINCE_CHARGED); + final long txPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_MOBILE_TX_DATA, STATS_SINCE_CHARGED); + if (rxPackets == 0 && txPackets == 0) { + continue; + } + final long radioActiveMs = u.getMobileRadioActiveTime(STATS_SINCE_CHARGED) / 1000; + final int radioActiveCount = u.getMobileRadioActiveCount(STATS_SINCE_CHARGED); + final double msPerPacket = (double) radioActiveMs / (rxPackets + txPackets); + if (msPerPacket == 0) { + continue; + } + uidMobileRadioStats.add( + new UidMobileRadioStats(uid, rxPackets, txPackets, radioActiveMs, + radioActiveCount, msPerPacket)); + } + uidMobileRadioStats.sort( + (lhs, rhs) -> Double.compare(rhs.millisecondsPerPacket, lhs.millisecondsPerPacket)); + return uidMobileRadioStats; + } } diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index d41a5fe08448..58f93364f686 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -597,20 +597,21 @@ public final class BatteryUsageStats implements Parcelable, Closeable { dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers()); dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers()); + pw.println(); } private void printPowerComponent(PrintWriter pw, String prefix, String label, double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) { StringBuilder sb = new StringBuilder(); sb.append(prefix).append(" ").append(label).append(": ") - .append(PowerCalculator.formatCharge(devicePowerMah)); + .append(BatteryStats.formatCharge(devicePowerMah)); if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) { sb.append(" ["); sb.append(BatteryConsumer.powerModelToString(powerModel)); sb.append("]"); } - sb.append(" apps: ").append(PowerCalculator.formatCharge(appsPowerMah)); + sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah)); if (durationMs != 0) { sb.append(" duration: "); BatteryStats.formatTimeMs(sb, durationMs); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 0257408b3e42..3d129417e53b 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -1333,7 +1333,7 @@ public class Environment { final Context context = AppGlobals.getInitialApplication(); final int uid = context.getApplicationInfo().uid; // Isolated processes and Instant apps are never allowed to be in scoped storage - if (Process.isIsolated(uid)) { + if (Process.isIsolated(uid) || Process.isSupplemental(uid)) { return false; } diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index 48e111647327..605171208fc1 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -26,8 +26,6 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.proto.ProtoOutputStream; -import com.android.internal.os.PowerCalculator; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -170,7 +168,7 @@ class PowerComponents { separator = " "; sb.append(key.toShortString()); sb.append("="); - sb.append(PowerCalculator.formatCharge(componentPower)); + sb.append(BatteryStats.formatCharge(componentPower)); if (durationMs != 0) { sb.append(" ("); @@ -194,7 +192,7 @@ class PowerComponents { separator = " "; sb.append(getCustomPowerComponentName(customComponentId)); sb.append("="); - sb.append(PowerCalculator.formatCharge(customComponentPower)); + sb.append(BatteryStats.formatCharge(customComponentPower)); } pw.print(sb); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 2fe062268112..17b5ec5ca01b 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -929,6 +929,7 @@ public class Process { * @hide */ @SystemApi(client = MODULE_LIBRARIES) + @TestApi public static final int toSupplementalUid(int uid) { return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID); } diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 1a082d103127..a1ff923e3b94 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -136,7 +136,7 @@ public final class UidBatteryConsumer extends BatteryConsumer { } sb.append(" ").append(processStateToString(processState)).append(": ") - .append(PowerCalculator.formatCharge(power)); + .append(BatteryStats.formatCharge(power)); } static UidBatteryConsumer create(BatteryConsumerData data) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 373179c57266..c597a1a6e7dc 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2509,12 +2509,12 @@ public class UserManager { } /** - * Checks if the calling context user is running in a profile. + * Checks if the calling context user is running in a profile. A profile is a user that + * typically has its own separate data but shares its UI with some parent user. For example, a + * {@link #isManagedProfile() managed profile} is a type of profile. * * @return whether the caller is in a profile. - * @hide */ - @SystemApi @UserHandleAware( requiresAnyOfPermissionsIfNotCallerProfileGroup = { android.Manifest.permission.MANAGE_USERS, diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl index 02db2749bbe8..68b5679919d6 100644 --- a/core/java/android/os/logcat/ILogcatManagerService.aidl +++ b/core/java/android/os/logcat/ILogcatManagerService.aidl @@ -22,7 +22,5 @@ package android.os.logcat; interface ILogcatManagerService { void startThread(in int uid, in int gid, in int pid, in int fd); void finishThread(in int uid, in int gid, in int pid, in int fd); - void approve(in int uid, in int gid, in int pid, in int fd); - void decline(in int uid, in int gid, in int pid, in int fd); } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 052bc6a0f3c3..4d65f39db0a9 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1331,8 +1331,10 @@ public class StorageManager { /** * Return the list of shared/external storage volumes currently available to - * the calling user and the user it shares media with - * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support + * the calling user and the user it shares media with. Please refer to + * <a href="https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support"> + * multi-user support</a> for more details. + * * <p> * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also * includes the volumes belonging to any user it shares media with diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bf94ab5537f0..464567b85270 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4501,6 +4501,13 @@ public final class Settings { public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout"; /** + * The amount of time in milliseconds before the device goes to sleep or begins to dream + * after a period of inactivity while it is docked. + * @hide + */ + public static final String SCREEN_OFF_TIMEOUT_DOCKED = "screen_off_timeout_docked"; + + /** * The screen backlight brightness between 0 and 255. */ @Readable @@ -10314,6 +10321,34 @@ public final class Settings { "nearby_fast_pair_settings_devices_component"; /** + * Current provider of the component for requesting ambient context consent. + * Default value in @string/config_defaultAmbientContextConsentComponent. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String AMBIENT_CONTEXT_CONSENT_COMPONENT = + "ambient_context_consent_component"; + + /** + * Current provider of the intent extra key for the caller's package name while + * requesting ambient context consent. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY = + "ambient_context_package_name_key"; + + /** + * Current provider of the intent extra key for the event code int array while + * requesting ambient context consent. + * Default value in @string/config_ambientContextEventArrayExtraKey. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY = + "ambient_context_event_array_key"; + + /** * Controls whether aware is enabled. * @hide */ diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 3ff0161b80d5..5ff926336751 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -2216,651 +2216,6 @@ public final class Telephony { } /** - * Columns for the "rcs_*" tables used by {@link android.telephony.ims.RcsMessageStore} classes. - * - * @hide - not meant for public use - */ - public interface RcsColumns { - // TODO(sahinc): Turn this to true once the schema finalizes, so that people can update - // their messaging databases. NOTE: move the switch/case update in MmsSmsDatabaseHelper to - // the latest version of the database before turning this flag to true. - boolean IS_RCS_TABLE_SCHEMA_CODE_COMPLETE = false; - - /** - * The authority for the content provider - */ - String AUTHORITY = "rcs"; - - /** - * The URI to start building upon to use {@link com.android.providers.telephony.RcsProvider} - */ - Uri CONTENT_AND_AUTHORITY = Uri.parse("content://" + AUTHORITY); - - /** - * The value to be used whenever a transaction that expects an integer to be returned - * failed. - */ - int TRANSACTION_FAILED = Integer.MIN_VALUE; - - /** - * The value that denotes a timestamp was not set before (e.g. a message that is not - * delivered yet will not have a DELIVERED_TIMESTAMP) - */ - long TIMESTAMP_NOT_SET = 0; - - /** - * The table that {@link android.telephony.ims.RcsThread} gets persisted to - */ - interface RcsThreadColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsThread}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String RCS_THREAD_URI_PART = "thread"; - - /** - * The URI to query or modify {@link android.telephony.ims.RcsThread} via the content - * provider. - */ - Uri RCS_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, RCS_THREAD_URI_PART); - - /** - * The unique identifier of an {@link android.telephony.ims.RcsThread} - */ - String RCS_THREAD_ID_COLUMN = "rcs_thread_id"; - } - - /** - * The table that {@link android.telephony.ims.Rcs1To1Thread} gets persisted to - */ - interface Rcs1To1ThreadColumns extends RcsThreadColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.Rcs1To1Thread}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String RCS_1_TO_1_THREAD_URI_PART = "p2p_thread"; - - /** - * The URI to query or modify {@link android.telephony.ims.Rcs1To1Thread}s via the - * content provider. Can also insert to this URI to create a new 1-to-1 thread. When - * performing an insert, ensure that the provided content values contain the other - * participant's ID under the key - * {@link RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN} - */ - Uri RCS_1_TO_1_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - RCS_1_TO_1_THREAD_URI_PART); - - /** - * The SMS/MMS thread to fallback to in case of an RCS outage - */ - String FALLBACK_THREAD_ID_COLUMN = "rcs_fallback_thread_id"; - } - - /** - * The table that {@link android.telephony.ims.RcsGroupThread} gets persisted to - */ - interface RcsGroupThreadColumns extends RcsThreadColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsGroupThread}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String RCS_GROUP_THREAD_URI_PART = "group_thread"; - - /** - * The URI to query or modify {@link android.telephony.ims.RcsGroupThread}s via the - * content provider - */ - Uri RCS_GROUP_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - RCS_GROUP_THREAD_URI_PART); - - /** - * The owner/admin of the {@link android.telephony.ims.RcsGroupThread} - */ - String OWNER_PARTICIPANT_COLUMN = "owner_participant"; - - /** - * The user visible name of the group - */ - String GROUP_NAME_COLUMN = "group_name"; - - /** - * The user visible icon of the group - */ - String GROUP_ICON_COLUMN = "group_icon"; - - /** - * The RCS conference URI for this group - */ - String CONFERENCE_URI_COLUMN = "conference_uri"; - } - - /** - * The view that enables polling from all types of RCS threads at once - */ - interface RcsUnifiedThreadColumns extends RcsThreadColumns, Rcs1To1ThreadColumns, - RcsGroupThreadColumns { - /** - * The type of this {@link android.telephony.ims.RcsThread} - */ - String THREAD_TYPE_COLUMN = "thread_type"; - - /** - * Integer returned as a result from a database query that denotes the thread is 1 to 1 - */ - int THREAD_TYPE_1_TO_1 = 0; - - /** - * Integer returned as a result from a database query that denotes the thread is 1 to 1 - */ - int THREAD_TYPE_GROUP = 1; - } - - /** - * The table that {@link android.telephony.ims.RcsParticipant} gets persisted to - */ - interface RcsParticipantColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsParticipant}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String RCS_PARTICIPANT_URI_PART = "participant"; - - /** - * The URI to query or modify {@link android.telephony.ims.RcsParticipant}s via the - * content provider - */ - Uri RCS_PARTICIPANT_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - RCS_PARTICIPANT_URI_PART); - - /** - * The unique identifier of the entry in the database - */ - String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id"; - - /** - * A foreign key on canonical_address table, also used by SMS/MMS - */ - String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id"; - - /** - * The user visible RCS alias for this participant. - */ - String RCS_ALIAS_COLUMN = "rcs_alias"; - } - - /** - * Additional constants to enable access to {@link android.telephony.ims.RcsParticipant} - * related data - */ - interface RcsParticipantHelpers extends RcsParticipantColumns { - /** - * The view that unifies "rcs_participant" and "canonical_addresses" tables for easy - * access to participant address. - */ - String RCS_PARTICIPANT_WITH_ADDRESS_VIEW = "rcs_participant_with_address_view"; - - /** - * The view that unifies "rcs_participant", "canonical_addresses" and - * "rcs_thread_participant" junction table to get full information on participants that - * contribute to threads. - */ - String RCS_PARTICIPANT_WITH_THREAD_VIEW = "rcs_participant_with_thread_view"; - } - - /** - * The table that {@link android.telephony.ims.RcsMessage} gets persisted to - */ - interface RcsMessageColumns { - /** - * Denotes the type of this message (i.e. - * {@link android.telephony.ims.RcsIncomingMessage} or - * {@link android.telephony.ims.RcsOutgoingMessage} - */ - String MESSAGE_TYPE_COLUMN = "rcs_message_type"; - - /** - * The unique identifier for the message in the database - i.e. the primary key. - */ - String MESSAGE_ID_COLUMN = "rcs_message_row_id"; - - /** - * The globally unique RCS identifier for the message. Please see 4.4.5.2 - GSMA - * RCC.53 (RCS Device API 1.6 Specification) - */ - String GLOBAL_ID_COLUMN = "rcs_message_global_id"; - - /** - * The subscription where this message was sent from/to. - */ - String SUB_ID_COLUMN = "sub_id"; - - /** - * The sending status of the message. - * @see android.telephony.ims.RcsMessage.RcsMessageStatus - */ - String STATUS_COLUMN = "status"; - - /** - * The creation timestamp of the message. - */ - String ORIGINATION_TIMESTAMP_COLUMN = "origination_timestamp"; - - /** - * The text content of the message. - */ - String MESSAGE_TEXT_COLUMN = "rcs_text"; - - /** - * The latitude content of the message, if it contains a location. - */ - String LATITUDE_COLUMN = "latitude"; - - /** - * The longitude content of the message, if it contains a location. - */ - String LONGITUDE_COLUMN = "longitude"; - } - - /** - * The table that additional information of {@link android.telephony.ims.RcsIncomingMessage} - * gets persisted to. - */ - interface RcsIncomingMessageColumns extends RcsMessageColumns { - /** - The path that should be used for referring to - * {@link android.telephony.ims.RcsIncomingMessage}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String INCOMING_MESSAGE_URI_PART = "incoming_message"; - - /** - * The URI to query incoming messages through - * {@link com.android.providers.telephony.RcsProvider} - */ - Uri INCOMING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - INCOMING_MESSAGE_URI_PART); - - /** - * The ID of the {@link android.telephony.ims.RcsParticipant} that sent this message - */ - String SENDER_PARTICIPANT_ID_COLUMN = "sender_participant"; - - /** - * The timestamp of arrival for this message. - */ - String ARRIVAL_TIMESTAMP_COLUMN = "arrival_timestamp"; - - /** - * The time when the recipient has read this message. - */ - String SEEN_TIMESTAMP_COLUMN = "seen_timestamp"; - } - - /** - * The table that additional information of {@link android.telephony.ims.RcsOutgoingMessage} - * gets persisted to. - */ - interface RcsOutgoingMessageColumns extends RcsMessageColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsOutgoingMessage}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String OUTGOING_MESSAGE_URI_PART = "outgoing_message"; - - /** - * The URI to query or modify {@link android.telephony.ims.RcsOutgoingMessage}s via the - * content provider - */ - Uri OUTGOING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - OUTGOING_MESSAGE_URI_PART); - } - - /** - * The delivery information of an {@link android.telephony.ims.RcsOutgoingMessage} - */ - interface RcsMessageDeliveryColumns extends RcsOutgoingMessageColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsOutgoingMessageDelivery}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String DELIVERY_URI_PART = "delivery"; - - /** - * The timestamp of delivery of this message. - */ - String DELIVERED_TIMESTAMP_COLUMN = "delivered_timestamp"; - - /** - * The time when the recipient has read this message. - */ - String SEEN_TIMESTAMP_COLUMN = "seen_timestamp"; - } - - /** - * The views that allow querying {@link android.telephony.ims.RcsIncomingMessage} and - * {@link android.telephony.ims.RcsOutgoingMessage} at the same time. - */ - interface RcsUnifiedMessageColumns extends RcsIncomingMessageColumns, - RcsOutgoingMessageColumns { - /** - * The path that is used to query all {@link android.telephony.ims.RcsMessage} in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String UNIFIED_MESSAGE_URI_PART = "message"; - - /** - * The URI to query all types of {@link android.telephony.ims.RcsMessage}s - */ - Uri UNIFIED_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - UNIFIED_MESSAGE_URI_PART); - - /** - * The name of the view that unites rcs_message and rcs_incoming_message tables. - */ - String UNIFIED_INCOMING_MESSAGE_VIEW = "unified_incoming_message_view"; - - /** - * The name of the view that unites rcs_message and rcs_outgoing_message tables. - */ - String UNIFIED_OUTGOING_MESSAGE_VIEW = "unified_outgoing_message_view"; - - /** - * The column that shows from which table the message entry came from. - */ - String MESSAGE_TYPE_COLUMN = "message_type"; - - /** - * Integer returned as a result from a database query that denotes that the message is - * an incoming message - */ - int MESSAGE_TYPE_INCOMING = 1; - - /** - * Integer returned as a result from a database query that denotes that the message is - * an outgoing message - */ - int MESSAGE_TYPE_OUTGOING = 0; - } - - /** - * The table that {@link android.telephony.ims.RcsFileTransferPart} gets persisted to. - */ - interface RcsFileTransferColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsFileTransferPart}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String FILE_TRANSFER_URI_PART = "file_transfer"; - - /** - * The URI to query or modify {@link android.telephony.ims.RcsFileTransferPart}s via the - * content provider - */ - Uri FILE_TRANSFER_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - FILE_TRANSFER_URI_PART); - - /** - * The globally unique file transfer ID for this RCS file transfer. - */ - String FILE_TRANSFER_ID_COLUMN = "rcs_file_transfer_id"; - - /** - * The RCS session ID for this file transfer. The ID is implementation dependent but - * should be unique. - */ - String SESSION_ID_COLUMN = "session_id"; - - /** - * The URI that points to the content of this file transfer - */ - String CONTENT_URI_COLUMN = "content_uri"; - - /** - * The file type of this file transfer in bytes. The validity of types is not enforced - * in {@link android.telephony.ims.RcsMessageStore} APIs. - */ - String CONTENT_TYPE_COLUMN = "content_type"; - - /** - * The size of the file transfer in bytes. - */ - String FILE_SIZE_COLUMN = "file_size"; - - /** - * Number of bytes that was successfully transmitted for this file transfer - */ - String SUCCESSFULLY_TRANSFERRED_BYTES = "transfer_offset"; - - /** - * The status of this file transfer - * @see android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus - */ - String TRANSFER_STATUS_COLUMN = "transfer_status"; - - /** - * The on-screen width of the file transfer, if it contains multi-media - */ - String WIDTH_COLUMN = "width"; - - /** - * The on-screen height of the file transfer, if it contains multi-media - */ - String HEIGHT_COLUMN = "height"; - - /** - * The duration of the content in milliseconds if this file transfer contains - * multi-media - */ - String DURATION_MILLIS_COLUMN = "duration"; - - /** - * The URI to the preview of the content of this file transfer - */ - String PREVIEW_URI_COLUMN = "preview_uri"; - - /** - * The type of the preview of the content of this file transfer. The validity of types - * is not enforced in {@link android.telephony.ims.RcsMessageStore} APIs. - */ - String PREVIEW_TYPE_COLUMN = "preview_type"; - } - - /** - * The table that holds the information for - * {@link android.telephony.ims.RcsGroupThreadEvent} and its subclasses. - */ - interface RcsThreadEventColumns { - /** - * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to - * refer to participant joined events (example URI: - * {@code content://rcs/group_thread/3/participant_joined_event}) - */ - String PARTICIPANT_JOINED_URI_PART = "participant_joined_event"; - - /** - * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to - * refer to participant left events. (example URI: - * {@code content://rcs/group_thread/3/participant_left_event/4}) - */ - String PARTICIPANT_LEFT_URI_PART = "participant_left_event"; - - /** - * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to - * refer to name changed events. (example URI: - * {@code content://rcs/group_thread/3/name_changed_event}) - */ - String NAME_CHANGED_URI_PART = "name_changed_event"; - - /** - * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to - * refer to icon changed events. (example URI: - * {@code content://rcs/group_thread/3/icon_changed_event}) - */ - String ICON_CHANGED_URI_PART = "icon_changed_event"; - - /** - * The unique ID of this event in the database, i.e. the primary key - */ - String EVENT_ID_COLUMN = "event_id"; - - /** - * The type of this event - * - * @see RcsEventTypes - */ - String EVENT_TYPE_COLUMN = "event_type"; - - /** - * The timestamp in milliseconds of when this event happened - */ - String TIMESTAMP_COLUMN = "origination_timestamp"; - - /** - * The participant that generated this event - */ - String SOURCE_PARTICIPANT_ID_COLUMN = "source_participant"; - - /** - * The receiving participant of this event if this was an - * {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} or - * {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent} - */ - String DESTINATION_PARTICIPANT_ID_COLUMN = "destination_participant"; - - /** - * The URI for the new icon of the group thread if this was an - * {@link android.telephony.ims.RcsGroupThreadIconChangedEvent} - */ - String NEW_ICON_URI_COLUMN = "new_icon_uri"; - - /** - * The URI for the new name of the group thread if this was an - * {@link android.telephony.ims.RcsGroupThreadNameChangedEvent} - */ - String NEW_NAME_COLUMN = "new_name"; - } - - /** - * The table that {@link android.telephony.ims.RcsParticipantAliasChangedEvent} gets - * persisted to - */ - interface RcsParticipantEventColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsParticipantAliasChangedEvent}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String ALIAS_CHANGE_EVENT_URI_PART = "alias_change_event"; - - /** - * The new alias of the participant - */ - String NEW_ALIAS_COLUMN = "new_alias"; - } - - /** - * These values are used in {@link com.android.providers.telephony.RcsProvider} to determine - * what kind of event is present in the storage. - */ - interface RcsEventTypes { - /** - * Integer constant that is stored in the - * {@link com.android.providers.telephony.RcsProvider} database that denotes the event - * is of type {@link android.telephony.ims.RcsParticipantAliasChangedEvent} - */ - int PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE = 1; - - /** - * Integer constant that is stored in the - * {@link com.android.providers.telephony.RcsProvider} database that denotes the event - * is of type {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} - */ - int PARTICIPANT_JOINED_EVENT_TYPE = 2; - - /** - * Integer constant that is stored in the - * {@link com.android.providers.telephony.RcsProvider} database that denotes the event - * is of type {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent} - */ - int PARTICIPANT_LEFT_EVENT_TYPE = 4; - - /** - * Integer constant that is stored in the - * {@link com.android.providers.telephony.RcsProvider} database that denotes the event - * is of type {@link android.telephony.ims.RcsGroupThreadIconChangedEvent} - */ - int ICON_CHANGED_EVENT_TYPE = 8; - - /** - * Integer constant that is stored in the - * {@link com.android.providers.telephony.RcsProvider} database that denotes the event - * is of type {@link android.telephony.ims.RcsGroupThreadNameChangedEvent} - */ - int NAME_CHANGED_EVENT_TYPE = 16; - } - - /** - * The view that allows unified querying across all events - */ - interface RcsUnifiedEventHelper extends RcsParticipantEventColumns, RcsThreadEventColumns { - /** - * The path that should be used for referring to - * {@link android.telephony.ims.RcsEvent}s in - * {@link com.android.providers.telephony.RcsProvider} URIs. - */ - String RCS_EVENT_QUERY_URI_PATH = "event"; - - /** - * The URI to query {@link android.telephony.ims.RcsEvent}s via the content provider. - */ - Uri RCS_EVENT_QUERY_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, - RCS_EVENT_QUERY_URI_PATH); - } - - /** - * Allows RCS specific canonical address handling. - */ - interface RcsCanonicalAddressHelper { - /** - * Returns the canonical address ID for a canonical address, if now row exists, this - * will add a row and return its ID. This helper works against the same table used by - * the SMS and MMS threads, but is accessible only by the phone process for use by RCS - * message storage. - * - * @throws IllegalArgumentException if unable to retrieve or create the canonical - * address entry. - */ - static long getOrCreateCanonicalAddressId( - ContentResolver contentResolver, String canonicalAddress) { - - Uri.Builder uriBuilder = CONTENT_AND_AUTHORITY.buildUpon(); - uriBuilder.appendPath("canonical-address"); - uriBuilder.appendQueryParameter("address", canonicalAddress); - Uri uri = uriBuilder.build(); - - try (Cursor cursor = contentResolver.query(uri, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndex(CanonicalAddressesColumns._ID)); - } else { - Rlog.e(TAG, "getOrCreateCanonicalAddressId returned no rows"); - } - } - - Rlog.e(TAG, "getOrCreateCanonicalAddressId failed"); - throw new IllegalArgumentException( - "Unable to find or allocate a canonical address ID"); - } - } - } - - /** * Contains all MMS messages. */ public static final class Mms implements BaseMmsColumns { diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl new file mode 100644 index 000000000000..4bb29b2ef16d --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 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 android.service.ambientcontext; + +parcelable AmbientContextDetectionResult;
\ No newline at end of file diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java new file mode 100644 index 000000000000..227194e60a7e --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2022 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 android.service.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.app.ambientcontext.AmbientContextEvent; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a {@code AmbientContextEvent} detection result reported by the detection service. + * + * @hide + */ +@SystemApi +public final class AmbientContextDetectionResult implements Parcelable { + + /** + * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String RESULT_RESPONSE_BUNDLE_KEY = + "android.app.ambientcontext.AmbientContextDetectionResultBundleKey"; + @NonNull private final List<AmbientContextEvent> mEvents; + @NonNull private final String mPackageName; + + AmbientContextDetectionResult( + @NonNull List<AmbientContextEvent> events, + @NonNull String packageName) { + this.mEvents = events; + AnnotationValidations.validate(NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate(NonNull.class, null, mPackageName); + } + + /** + * A list of detected event. + */ + @SuppressLint("ConcreteCollection") + public @NonNull List<AmbientContextEvent> getEvents() { + return mEvents; + } + + /** + * The package to deliver the response to. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + @Override + public String toString() { + return "AmbientContextEventResponse { " + + "events = " + mEvents + ", " + "packageName = " + mPackageName + " }"; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + dest.writeByte(flg); + dest.writeParcelableList(mEvents, flags); + dest.writeString(mPackageName); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextDetectionResult(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); + ArrayList<AmbientContextEvent> events = new ArrayList<>(); + in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(), + AmbientContextEvent.class); + String packageName = in.readString(); + + this.mEvents = events; + AnnotationValidations.validate( + NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate( + NonNull.class, null, mPackageName); + } + + public static final @NonNull Creator<AmbientContextDetectionResult> CREATOR = + new Creator<AmbientContextDetectionResult>() { + @Override + public AmbientContextDetectionResult[] newArray(int size) { + return new AmbientContextDetectionResult[size]; + } + + @Override + public AmbientContextDetectionResult createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextDetectionResult(in); + } + }; + + /** + * A builder for {@link AmbientContextDetectionResult} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @NonNull ArrayList<AmbientContextEvent> mEvents; + private @NonNull String mPackageName; + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Adds an event to the builder. + */ + public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) { + checkNotUsed(); + if (mEvents == null) { + mBuilderFieldsSet |= 0x1; + mEvents = new ArrayList<>(); + } + mEvents.add(value); + return this; + } + + /** + * The package to deliver the response to. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPackageName = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextDetectionResult build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEvents = new ArrayList<>(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPackageName = ""; + } + AmbientContextDetectionResult o = new AmbientContextDetectionResult( + mEvents, + mPackageName); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java index dccfe3693b35..6224aa1d102e 100644 --- a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java @@ -16,13 +16,13 @@ package android.service.ambientcontext; +import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; -import android.app.ambientcontext.AmbientContextEventResponse; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; @@ -64,15 +64,6 @@ public abstract class AmbientContextDetectionService extends Service { public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; - /** - * The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation - * should set bundle result with this key. - * - * @hide - */ - public static final String RESPONSE_BUNDLE_KEY = - "android.service.ambientcontext.EventResponseKey"; - @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { @@ -82,19 +73,30 @@ public abstract class AmbientContextDetectionService extends Service { @Override public void startDetection( @NonNull AmbientContextEventRequest request, String packageName, - RemoteCallback callback) { + RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { Objects.requireNonNull(request); - Objects.requireNonNull(callback); - Consumer<AmbientContextEventResponse> consumer = - response -> { + Objects.requireNonNull(packageName); + Objects.requireNonNull(detectionResultCallback); + Objects.requireNonNull(statusCallback); + Consumer<AmbientContextDetectionResult> detectionResultConsumer = + result -> { Bundle bundle = new Bundle(); bundle.putParcelable( - AmbientContextDetectionService.RESPONSE_BUNDLE_KEY, - response); - callback.sendResult(bundle); + AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, + result); + detectionResultCallback.sendResult(bundle); + }; + Consumer<AmbientContextDetectionServiceStatus> statusConsumer = + status -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionServiceStatus + .STATUS_RESPONSE_BUNDLE_KEY, + status); + statusCallback.sendResult(bundle); }; AmbientContextDetectionService.this.onStartDetection( - request, packageName, consumer); + request, packageName, detectionResultConsumer, statusConsumer); Slog.d(TAG, "startDetection " + request); } @@ -104,29 +106,52 @@ public abstract class AmbientContextDetectionService extends Service { Objects.requireNonNull(packageName); AmbientContextDetectionService.this.onStopDetection(packageName); } + + /** {@inheritDoc} */ + @Override + public void queryServiceStatus( + @AmbientContextEvent.EventCode int[] eventTypes, + String packageName, + RemoteCallback callback) { + Objects.requireNonNull(eventTypes); + Objects.requireNonNull(packageName); + Objects.requireNonNull(callback); + Consumer<AmbientContextDetectionServiceStatus> consumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionServiceStatus + .STATUS_RESPONSE_BUNDLE_KEY, + response); + callback.sendResult(bundle); + }; + AmbientContextDetectionService.this.onQueryServiceStatus( + eventTypes, packageName, consumer); + } }; } return null; } /** - * Starts detection and provides detected events to the consumer. The ongoing detection will - * keep running, until onStopDetection is called. If there were previously requested + * Starts detection and provides detected events to the statusConsumer. The ongoing detection + * will keep running, until onStopDetection is called. If there were previously requested * detection from the same package, the previous request will be replaced with the new request. * The implementation should keep track of whether the user consented each requested - * AmbientContextEvent for the app. If not consented, the response should set status - * STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user - * to the consent screen. + * AmbientContextEvent for the app. If not consented, the statusConsumer should get a response + * with STATUS_ACCESS_DENIED. * - * @param request The request with events to detect, optional detection window and other - * options. + * @param request The request with events to detect. * @param packageName the requesting app's package name - * @param consumer the consumer for the detected event + * @param detectionResultConsumer the consumer for the detected event + * @param statusConsumer the consumer for the service status. */ + @BinderThread public abstract void onStartDetection( @NonNull AmbientContextEventRequest request, @NonNull String packageName, - @NonNull Consumer<AmbientContextEventResponse> consumer); + @NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer, + @NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer); /** * Stops detection of the events. Events that are not being detected will be ignored. @@ -134,4 +159,19 @@ public abstract class AmbientContextDetectionService extends Service { * @param packageName stops detection for the given package. */ public abstract void onStopDetection(@NonNull String packageName); + + /** + * Called when a query for the detection status occurs. The implementation should check + * the detection status of the requested events for the package, and provide results in a + * {@link AmbientContextDetectionServiceStatus} for the consumer. + * + * @param eventTypes The events to check for status. + * @param packageName the requesting app's package name + * @param consumer the consumer for the query results + */ + @BinderThread + public abstract void onQueryServiceStatus( + @NonNull int[] eventTypes, + @NonNull String packageName, + @NonNull Consumer<AmbientContextDetectionServiceStatus> consumer); } diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl new file mode 100644 index 000000000000..979cf7b69ddf --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 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 android.service.ambientcontext; + +parcelable AmbientContextDetectionServiceStatus;
\ No newline at end of file diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java new file mode 100644 index 000000000000..3e92f39893de --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 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 android.service.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.AmbientContextManager.StatusCode; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +/** + * Represents a status for the {@code AmbientContextDetectionService}. + * + * @hide + */ +@SystemApi +public final class AmbientContextDetectionServiceStatus implements Parcelable { + /** + * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String STATUS_RESPONSE_BUNDLE_KEY = + "android.app.ambientcontext.AmbientContextServiceStatusBundleKey"; + + @StatusCode private final int mStatusCode; + @NonNull private final String mPackageName; + + AmbientContextDetectionServiceStatus( + @StatusCode int statusCode, + @NonNull String packageName) { + this.mStatusCode = statusCode; + AnnotationValidations.validate(StatusCode.class, null, mStatusCode); + this.mPackageName = packageName; + } + + /** + * The status of the service. + */ + public @StatusCode int getStatusCode() { + return mStatusCode; + } + + /** + * The package to deliver the response to. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + @Override + public String toString() { + return "AmbientContextDetectionServiceStatus { " + "statusCode = " + mStatusCode + ", " + + "packageName = " + mPackageName + " }"; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + dest.writeByte(flg); + dest.writeInt(mStatusCode); + dest.writeString(mPackageName); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextDetectionServiceStatus(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); + int statusCode = in.readInt(); + String packageName = in.readString(); + + this.mStatusCode = statusCode; + AnnotationValidations.validate( + StatusCode.class, null, mStatusCode); + this.mPackageName = packageName; + AnnotationValidations.validate( + NonNull.class, null, mPackageName); + } + + public static final @NonNull Creator<AmbientContextDetectionServiceStatus> CREATOR = + new Creator<AmbientContextDetectionServiceStatus>() { + @Override + public AmbientContextDetectionServiceStatus[] newArray(int size) { + return new AmbientContextDetectionServiceStatus[size]; + } + + @Override + public AmbientContextDetectionServiceStatus createFromParcel( + @NonNull android.os.Parcel in) { + return new AmbientContextDetectionServiceStatus(in); + } + }; + + /** + * A builder for {@link AmbientContextDetectionServiceStatus} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @StatusCode int mStatusCode; + private @NonNull String mPackageName; + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Sets the status of the service. + */ + public @NonNull Builder setStatusCode(@StatusCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mStatusCode = value; + return this; + } + + /** + * The package to deliver the response to. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPackageName = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextDetectionServiceStatus build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mStatusCode = AmbientContextManager.STATUS_UNKNOWN; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPackageName = ""; + } + AmbientContextDetectionServiceStatus o = new AmbientContextDetectionServiceStatus( + mStatusCode, + mPackageName); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl index 1c6e25efeabe..50c89c0832eb 100644 --- a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl +++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl @@ -26,6 +26,8 @@ import android.os.RemoteCallback; */ oneway interface IAmbientContextDetectionService { void startDetection(in AmbientContextEventRequest request, in String packageName, - in RemoteCallback callback); + in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback); void stopDetection(in String packageName); + void queryServiceStatus(in int[] eventTypes, in String packageName, + in RemoteCallback callback); }
\ No newline at end of file diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index ae39d3d3c2da..b9e60a11a8f3 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -256,6 +256,8 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_CHANNEL_REMOVED = 20; /** Notification was canceled due to the app's storage being cleared */ public static final int REASON_CLEAR_DATA = 21; + /** Notification was canceled due to an assistant adjustment update. */ + public static final int REASON_ASSISTANT_CANCEL = 22; /** * @hide @@ -279,7 +281,10 @@ public abstract class NotificationListenerService extends Service { REASON_UNAUTOBUNDLED, REASON_CHANNEL_BANNED, REASON_SNOOZED, - REASON_TIMEOUT + REASON_TIMEOUT, + REASON_CHANNEL_REMOVED, + REASON_CLEAR_DATA, + REASON_ASSISTANT_CANCEL, }) public @interface NotificationCancelReason{}; diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 502558e355e6..e075c05279a2 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -496,6 +496,17 @@ public class SpeechRecognizer { Objects.requireNonNull(recognizerIntent, "intent must not be null"); Objects.requireNonNull(supportListener, "listener must not be null"); + if (DBG) { + Slog.i(TAG, "#checkRecognitionSupport called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, Pair.create(recognizerIntent, supportListener))); } @@ -510,7 +521,17 @@ public class SpeechRecognizer { */ public void triggerModelDownload(@NonNull Intent recognizerIntent) { Objects.requireNonNull(recognizerIntent, "intent must not be null"); - putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD)); + if (DBG) { + Slog.i(TAG, "#triggerModelDownload called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } + putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent)); } /** @@ -625,7 +646,6 @@ public class SpeechRecognizer { } try { mService.triggerModelDownload(recognizerIntent); - if (DBG) Log.d(TAG, "service download support command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "downloadModel() failed", e); mListener.onError(ERROR_CLIENT); @@ -705,6 +725,9 @@ public class SpeechRecognizer { } private synchronized boolean maybeInitializeManagerService() { + if (DBG) { + Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService); + } if (mManagerService != null) { return true; } @@ -712,8 +735,13 @@ public class SpeechRecognizer { mManagerService = IRecognitionServiceManager.Stub.asInterface( ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); - if (mManagerService == null && mListener != null) { - mListener.onError(ERROR_CLIENT); + if (DBG) { + Log.i(TAG, "#maybeInitializeManagerService instantiated =" + mManagerService); + } + if (mManagerService == null) { + if (mListener != null) { + mListener.onError(ERROR_CLIENT); + } return false; } return true; diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 246a8c9d17d3..7d8e99840fb1 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -42,6 +42,7 @@ import android.hardware.display.BrightnessInfo; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -1856,6 +1857,19 @@ public final class Display { } /** + * Returns whether/how the specified display supports DISPLAY_DECORATION. + * + * Composition.DISPLAY_DECORATION is a special layer type which is used to + * render the screen decorations (i.e. antialiased rounded corners and + * cutouts) while taking advantage of specific hardware. + * + * @hide + */ + public DisplayDecorationSupport getDisplayDecorationSupport() { + return mGlobal.getDisplayDecorationSupport(mDisplayId); + } + + /** * A mode supported by a given display. * * @see Display#getSupportedModes() diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c5ccc18b0cd4..36baa0447176 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -941,4 +941,14 @@ interface IWindowManager * @hide */ void unregisterTaskFpsCallback(in IOnFpsCallbackListener listener); + + /** + * Take a snapshot using the same path that's used for Recents. This is used for Testing only. + * + * @param taskId to take the snapshot of + * + * Returns a bitmap of the screenshot or {@code null} if it was unable to screenshot. + * @hide + */ + Bitmap snapshotTaskForRecents(int taskId); } diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java index b5cd89cfa4e1..37f858abf818 100644 --- a/core/java/android/view/OnBackInvokedCallback.java +++ b/core/java/android/view/OnBackInvokedCallback.java @@ -18,6 +18,7 @@ package android.view; import android.app.Activity; import android.app.Dialog; +import android.window.BackEvent; /** * Interface for applications to register back invocation callbacks. This allows the client @@ -46,14 +47,12 @@ public interface OnBackInvokedCallback { /** * Called on back gesture progress. * - * @param touchX Absolute X location of the touch point. - * @param touchY Absolute Y location of the touch point. - * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param backEvent An {@link android.window.BackEvent} object describing the progress event. * + * @see android.window.BackEvent * @hide */ - // TODO(b/210539672): combine back progress params into BackEvent. - default void onBackProgressed(int touchX, int touchY, float progress) { }; + default void onBackProgressed(BackEvent backEvent) { }; /** * Called when a back gesture or back button press has been cancelled. diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 7f115fad5735..3cd1e0fd71d2 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -49,6 +49,7 @@ import android.hardware.HardwareBuffer; import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Build; import android.os.IBinder; import android.os.Parcel; @@ -235,7 +236,8 @@ public final class SurfaceControl implements Parcelable { float shadowRadius); private static native void nativeSetGlobalShadowSettings(@Size(4) float[] ambientColor, @Size(4) float[] spotColor, float lightPosY, float lightPosZ, float lightRadius); - private static native boolean nativeGetDisplayDecorationSupport(IBinder displayToken); + private static native DisplayDecorationSupport nativeGetDisplayDecorationSupport( + IBinder displayToken); private static native void nativeSetFrameRate(long transactionObj, long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); @@ -2694,16 +2696,18 @@ public final class SurfaceControl implements Parcelable { } /** - * Returns whether a display supports DISPLAY_DECORATION. + * Returns whether/how a display supports DISPLAY_DECORATION. * * @param displayToken * The token for the display. * - * @return Whether the display supports DISPLAY_DECORATION. + * @return A class describing how the display supports DISPLAY_DECORATION or null if it does + * not. * + * TODO (b/218524164): Move this out of SurfaceControl. * @hide */ - public static boolean getDisplayDecorationSupport(IBinder displayToken) { + public static DisplayDecorationSupport getDisplayDecorationSupport(IBinder displayToken) { return nativeGetDisplayDecorationSupport(displayToken); } @@ -2779,9 +2783,13 @@ public final class SurfaceControl implements Parcelable { * is allowed as a convenience. */ public Transaction() { - mNativeObject = nativeCreateTransaction(); - mFreeNativeResources - = sRegistry.registerNativeAllocation(this, mNativeObject); + this(nativeCreateTransaction()); + } + + private Transaction(long nativeObject) { + mNativeObject = nativeObject; + mFreeNativeResources = + sRegistry.registerNativeAllocation(this, mNativeObject); } private Transaction(Parcel in) { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 83a59e77efed..a2fcf80af656 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -527,20 +527,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mRequestedVisible = false; updateSurface(); - tryReleaseSurfaces(); // We don't release this as part of releaseSurfaces as // that is also called on transient visibility changes. We can't // recreate this Surface, so only release it when we are fully // detached. - if (mSurfacePackage != null) { - final SurfaceControl sc = mSurfacePackage.getSurfaceControl(); - if (sc != null && sc.isValid()) { - mTmpTransaction.reparent(sc, null).apply(); - } - mSurfacePackage.release(); - mSurfacePackage = null; - } + tryReleaseSurfaces(true /* releaseSurfacePackage*/); mHaveFrame = false; super.onDetachedFromWindow(); @@ -871,7 +863,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall return t; } - private void tryReleaseSurfaces() { + private void tryReleaseSurfaces(boolean releaseSurfacePackage) { mSurfaceAlpha = 1f; synchronized (mSurfaceControlLock) { @@ -881,9 +873,26 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mBlastBufferQueue = null; } - ViewRootImpl viewRoot = getViewRootImpl(); Transaction transaction = new Transaction(); - releaseSurfaces(transaction); + if (mSurfaceControl != null) { + transaction.remove(mSurfaceControl); + mSurfaceControl = null; + } + if (mBackgroundControl != null) { + transaction.remove(mBackgroundControl); + mBackgroundControl = null; + } + if (mBlastSurfaceControl != null) { + transaction.remove(mBlastSurfaceControl); + mBlastSurfaceControl = null; + } + + if (releaseSurfacePackage && mSurfacePackage != null) { + mSurfacePackage.release(); + mSurfacePackage = null; + } + + ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null) { viewRoot.applyTransactionOnDraw(transaction); } else { @@ -892,22 +901,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } - private void releaseSurfaces(Transaction transaction) { - if (mSurfaceControl != null) { - transaction.remove(mSurfaceControl); - mSurfaceControl = null; - } - if (mBackgroundControl != null) { - transaction.remove(mBackgroundControl); - mBackgroundControl = null; - } - if (mBlastSurfaceControl != null) { - transaction.remove(mBlastSurfaceControl); - mBlastSurfaceControl = null; - } - } - - // The position update listener is used to safely share the surface size between render thread // workers and the UI thread. Both threads need to know the surface size to determine the scale. // The parent layer scales the surface size to view size. The child (BBQ) layer scales @@ -1048,7 +1041,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { notifySurfaceDestroyed(); - tryReleaseSurfaces(); + tryReleaseSurfaces(false /* releaseSurfacePackage*/); return; } @@ -1189,7 +1182,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } finally { mIsCreating = false; if (mSurfaceControl != null && !mSurfaceCreated) { - tryReleaseSurfaces(); + tryReleaseSurfaces(false /* releaseSurfacePackage*/); } } } catch (Exception ex) { @@ -1835,43 +1828,24 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @param p The SurfacePackage to embed. */ public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) { - setChildSurfacePackage(p, false /* applyTransactionOnDraw */); - } - - /** - * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be - * synchronized with the ViewRootImpl frame. - * @hide - */ - public void setChildSurfacePackageOnDraw( - @NonNull SurfaceControlViewHost.SurfacePackage p) { - setChildSurfacePackage(p, true /* applyTransactionOnDraw */); - } - - /** - * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately. - */ - private void setChildSurfacePackage( - @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) { final SurfaceControl lastSc = mSurfacePackage != null ? mSurfacePackage.getSurfaceControl() : null; + final SurfaceControl.Transaction transaction = new Transaction(); if (mSurfaceControl != null) { if (lastSc != null) { - mTmpTransaction.reparent(lastSc, null); + transaction.reparent(lastSc, null); mSurfacePackage.release(); } - reparentSurfacePackage(mTmpTransaction, p); - applyTransaction(applyTransactionOnDraw); + reparentSurfacePackage(transaction, p); + final ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.applyTransactionOnDraw(transaction); + } else { + transaction.apply(); + } } mSurfacePackage = p; - } - - private void applyTransaction(boolean applyTransactionOnDraw) { - if (applyTransactionOnDraw) { - getViewRootImpl().applyTransactionOnDraw(mTmpTransaction); - } else { - mTmpTransaction.apply(); - } + invalidate(); } private void reparentSurfacePackage(SurfaceControl.Transaction t, diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 777e89d145b2..d4dafbd7b137 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4169,19 +4169,22 @@ public final class ViewRootImpl implements ViewParent, + " didProduceBuffer=" + didProduceBuffer); } + Transaction tmpTransaction = new Transaction(); + tmpTransaction.merge(mRtBLASTSyncTransaction); + // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next // draw attempt. The next transaction and transaction complete callback were only set // for the current draw attempt. if (!didProduceBuffer) { mBlastBufferQueue.setSyncTransaction(null); - // Apply the transactions that were sent to mergeWithNextTransaction since the + // Get the transactions that were sent to mergeWithNextTransaction since the // frame didn't draw on this vsync. It's possible the frame will draw later, but // it's better to not be sync than to block on a frame that may never come. - mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum); + Transaction pendingTransactions = mBlastBufferQueue.gatherPendingTransactions( + mRtLastAttemptedDrawFrameNum); + tmpTransaction.merge(pendingTransactions); } - Transaction tmpTransaction = new Transaction(); - tmpTransaction.merge(mRtBLASTSyncTransaction); // Post at front of queue so the buffer can be processed immediately and allow RT // to continue processing new buffers. If RT tries to process buffers before the sync // buffer is applied, the new buffers will not get acquired and could result in a @@ -10838,6 +10841,7 @@ public final class ViewRootImpl implements ViewParent, private void unregisterCompatOnBackInvokedCallback() { if (mCompatOnBackInvokedCallback != null) { mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback); + mCompatOnBackInvokedCallback = null; } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index ca7f90080c6c..771d40bdf655 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -97,6 +97,7 @@ import android.content.ClipData; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; @@ -4886,4 +4887,21 @@ public interface WindowManager extends ViewManager { */ @SystemApi default void unregisterTaskFpsCallback(@NonNull TaskFpsCallback callback) {} + + /** + * Take a snapshot using the same path that's used for Recents. This is used for Testing only. + * + * @param taskId to take the snapshot of + * + * @return a bitmap of the screenshot or {@code null} if it was unable to screenshot. The + * screenshot can fail if the taskId is invalid or if there's no SurfaceControl associated with + * that task. + * + * @hide + */ + @TestApi + @Nullable + default Bitmap snapshotTaskForRecents(@IntRange(from = 0) int taskId) { + return null; + } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index c16703ef50ef..f4353eb5b397 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -32,6 +32,7 @@ import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; @@ -439,4 +440,14 @@ public final class WindowManagerImpl implements WindowManager { } catch (RemoteException e) { } } + + @Override + public Bitmap snapshotTaskForRecents(int taskId) { + try { + return WindowManagerGlobal.getWindowManagerService().snapshotTaskForRecents(taskId); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + return null; + } } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index e54ed18c51a6..c6cd2e81bbf5 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1773,8 +1773,7 @@ public final class AccessibilityManager { * @param userId The user Id. * @hide */ - @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) - public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1783,7 +1782,7 @@ public final class AccessibilityManager { } } try { - service.setSystemAudioCaptioningRequested(isEnabled, userId); + service.setSystemAudioCaptioningEnabled(isEnabled, userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -1796,7 +1795,7 @@ public final class AccessibilityManager { * @return the system audio caption UI enabled state. * @hide */ - public boolean isSystemAudioCaptioningUiRequested(int userId) { + public boolean isSystemAudioCaptioningUiEnabled(int userId) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1805,7 +1804,7 @@ public final class AccessibilityManager { } } try { - return service.isSystemAudioCaptioningUiRequested(userId); + return service.isSystemAudioCaptioningUiEnabled(userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -1818,8 +1817,7 @@ public final class AccessibilityManager { * @param userId The user Id. * @hide */ - @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) - public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) { final IAccessibilityManager service; synchronized (mLock) { service = getServiceLocked(); @@ -1828,7 +1826,7 @@ public final class AccessibilityManager { } } try { - service.setSystemAudioCaptioningUiRequested(isEnabled, userId); + service.setSystemAudioCaptioningUiEnabled(isEnabled, userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java index 4f9781b6b6af..e960bec67763 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -152,7 +152,7 @@ public class CaptioningManager { /** * @return the system audio caption enabled state. */ - public final boolean isSystemAudioCaptioningRequested() { + public final boolean isSystemAudioCaptioningEnabled() { return Secure.getIntForUser(mContentResolver, Secure.ODI_CAPTIONS_ENABLED, SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED ? 1 : 0, mContext.getUserId()) == 1; } @@ -169,9 +169,9 @@ public class CaptioningManager { */ @SystemApi @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) - public final void setSystemAudioCaptioningRequested(boolean isEnabled) { + public final void setSystemAudioCaptioningEnabled(boolean isEnabled) { if (mAccessibilityManager != null) { - mAccessibilityManager.setSystemAudioCaptioningRequested(isEnabled, + mAccessibilityManager.setSystemAudioCaptioningEnabled(isEnabled, mContext.getUserId()); } } @@ -179,9 +179,9 @@ public class CaptioningManager { /** * @return the system audio caption UI enabled state. */ - public final boolean isSystemAudioCaptioningUiRequested() { + public final boolean isSystemAudioCaptioningUiEnabled() { return mAccessibilityManager != null - && mAccessibilityManager.isSystemAudioCaptioningUiRequested(mContext.getUserId()); + && mAccessibilityManager.isSystemAudioCaptioningUiEnabled(mContext.getUserId()); } /** @@ -196,9 +196,9 @@ public class CaptioningManager { */ @SystemApi @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) - public final void setSystemAudioCaptioningUiRequested(boolean isEnabled) { + public final void setSystemAudioCaptioningUiEnabled(boolean isEnabled) { if (mAccessibilityManager != null) { - mAccessibilityManager.setSystemAudioCaptioningUiRequested(isEnabled, + mAccessibilityManager.setSystemAudioCaptioningUiEnabled(isEnabled, mContext.getUserId()); } } @@ -300,7 +300,7 @@ public class CaptioningManager { } private void notifySystemAudioCaptionChanged() { - final boolean enabled = isSystemAudioCaptioningRequested(); + final boolean enabled = isSystemAudioCaptioningEnabled(); synchronized (mListeners) { for (CaptioningChangeListener listener : mListeners) { listener.onSystemAudioCaptioningChanged(enabled); @@ -309,7 +309,7 @@ public class CaptioningManager { } private void notifySystemAudioCaptionUiChanged() { - final boolean enabled = isSystemAudioCaptioningUiRequested(); + final boolean enabled = isSystemAudioCaptioningUiEnabled(); synchronized (mListeners) { for (CaptioningChangeListener listener : mListeners) { listener.onSystemAudioCaptioningUiChanged(enabled); @@ -686,7 +686,7 @@ public class CaptioningManager { * @param isEnabled The system audio captioning enabled state. * @param userId The user Id. */ - void setSystemAudioCaptioningRequested(boolean isEnabled, int userId); + void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId); /** * Gets the system audio caption UI enabled state. @@ -694,7 +694,7 @@ public class CaptioningManager { * @param userId The user Id. * @return the system audio caption UI enabled state. */ - boolean isSystemAudioCaptioningUiRequested(int userId); + boolean isSystemAudioCaptioningUiEnabled(int userId); /** * Sets the system audio caption UI enabled state. @@ -702,6 +702,6 @@ public class CaptioningManager { * @param isEnabled The system audio captioning UI enabled state. * @param userId The user Id. */ - void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId); + void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId); } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 645ddf5542f7..418132a6e597 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -101,13 +101,11 @@ interface IAccessibilityManager { boolean isAudioDescriptionByDefaultEnabled(); - // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION - // System process only - void setSystemAudioCaptioningRequested(boolean isEnabled, int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)") + void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId); - boolean isSystemAudioCaptioningUiRequested(int userId); + boolean isSystemAudioCaptioningUiEnabled(int userId); - // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION - // System process only - void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)") + void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f480b24bdba3..c713a54fdf7d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -115,6 +115,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Central system API to the overall input method framework (IMF) architecture, @@ -426,6 +427,8 @@ public final class InputMethodManager { int mCursorSelEnd; int mCursorCandStart; int mCursorCandEnd; + int mInitialSelStart; + int mInitialSelEnd; /** * The instance that has previously been sent to the input method. @@ -468,6 +471,13 @@ public final class InputMethodManager { @Nullable @GuardedBy("mH") private InputMethodSessionWrapper mCurrentInputMethodSession = null; + /** + * Encapsulates IPCs to the currently connected AccessibilityServices. + */ + @Nullable + @GuardedBy("mH") + private final SparseArray<InputMethodSessionWrapper> mAccessibilityInputMethodSession = + new SparseArray<>(); InputChannel mCurChannel; ImeInputEventSender mCurSender; @@ -499,6 +509,8 @@ public final class InputMethodManager { static final int MSG_TIMEOUT_INPUT_EVENT = 6; static final int MSG_FLUSH_INPUT_EVENT = 7; static final int MSG_REPORT_FULLSCREEN_MODE = 10; + static final int MSG_BIND_ACCESSIBILITY_SERVICE = 11; + static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12; private static boolean isAutofillUIShowing(View servedView) { AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class); @@ -626,6 +638,7 @@ public final class InputMethodManager { if (mCurrentInputMethodSession != null) { mCurrentInputMethodSession.finishInput(); } + forAccessibilitySessions(InputMethodSessionWrapper::finishInput); } } @@ -881,6 +894,7 @@ public final class InputMethodManager { if (mBindSequence != sequence) { return; } + clearAllAccessibilityBindingLocked(); clearBindingLocked(); // If we were actively using the last input method, then // we would like to re-connect to the next input method. @@ -896,6 +910,73 @@ public final class InputMethodManager { } return; } + case MSG_BIND_ACCESSIBILITY_SERVICE: { + final int id = msg.arg1; + final InputBindResult res = (InputBindResult) msg.obj; + if (DEBUG) { + Log.i(TAG, "handleMessage: MSG_BIND_ACCESSIBILITY " + res.sequence + + "," + res.id); + } + synchronized (mH) { + if (mBindSequence < 0 || mBindSequence != res.sequence) { + Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence + + ", given seq=" + res.sequence); + if (res.channel != null && res.channel != mCurChannel) { + res.channel.dispose(); + } + return; + } + + // Since IMM can start inputting text before a11y sessions are back, + // we send a notification so that the a11y service knows the session is + // registered and update the a11y service with the current cursor positions. + if (res.accessibilitySessions != null) { + InputMethodSessionWrapper wrapper = + InputMethodSessionWrapper.createOrNull( + res.accessibilitySessions.get(id)); + if (wrapper != null) { + mAccessibilityInputMethodSession.put(id, wrapper); + if (mServedInputConnection != null) { + wrapper.updateSelection(mInitialSelStart, mInitialSelEnd, + mCursorSelStart, mCursorSelEnd, mCursorCandStart, + mCursorCandEnd); + } else { + // If an a11y service binds before input starts, we should still + // send a notification because the a11y service doesn't know it + // binds before or after input starts, it may wonder if it binds + // after input starts, why it doesn't receive a notification of + // the current cursor positions. + wrapper.updateSelection(-1, -1, + -1, -1, -1, + -1); + } + } + } + mBindSequence = res.sequence; + } + startInputInner(StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS, null, + 0, 0, 0); + return; + } + case MSG_UNBIND_ACCESSIBILITY_SERVICE: { + final int sequence = msg.arg1; + final int id = msg.arg2; + if (DEBUG) { + Log.i(TAG, "handleMessage: MSG_UNBIND_ACCESSIBILITY_SERVICE " + + sequence + " id=" + id); + } + synchronized (mH) { + if (mBindSequence != sequence) { + if (DEBUG) { + Log.i(TAG, "mBindSequence =" + mBindSequence + " sequence =" + + sequence + " id=" + id); + } + return; + } + clearAccessibilityBindingLocked(id); + } + return; + } case MSG_SET_ACTIVE: { final boolean active = msg.arg1 != 0; final boolean fullscreen = msg.arg2 != 0; @@ -996,11 +1077,21 @@ public final class InputMethodManager { } @Override + public void onBindAccessibilityService(InputBindResult res, int id) { + mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget(); + } + + @Override public void onUnbindMethod(int sequence, @UnbindReason int unbindReason) { mH.obtainMessage(MSG_UNBIND, sequence, unbindReason).sendToTarget(); } @Override + public void onUnbindAccessibilityService(int sequence, int id) { + mH.obtainMessage(MSG_UNBIND_ACCESSIBILITY_SERVICE, sequence, id).sendToTarget(); + } + + @Override public void setActive(boolean active, boolean fullscreen, boolean reportToImeController) { mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController).sendToTarget(); @@ -1420,16 +1511,36 @@ public final class InputMethodManager { /** * Reset all of the state associated with being bound to an input method. */ + @GuardedBy("mH") void clearBindingLocked() { if (DEBUG) Log.v(TAG, "Clearing binding!"); clearConnectionLocked(); setInputChannelLocked(null); + // We only reset sequence number for input method, but not accessibility. mBindSequence = -1; mCurId = null; mCurMethod = null; // for @UnsupportedAppUsage mCurrentInputMethodSession = null; } + /** + * Reset all of the state associated with being bound to an accessibility service. + */ + @GuardedBy("mH") + void clearAccessibilityBindingLocked(int id) { + if (DEBUG) Log.v(TAG, "Clearing accessibility binding " + id); + mAccessibilityInputMethodSession.remove(id); + } + + /** + * Reset all of the state associated with being bound to all ccessibility services. + */ + @GuardedBy("mH") + void clearAllAccessibilityBindingLocked() { + if (DEBUG) Log.v(TAG, "Clearing all accessibility bindings"); + mAccessibilityInputMethodSession.clear(); + } + void setInputChannelLocked(InputChannel channel) { if (mCurChannel == channel) { return; @@ -1938,6 +2049,8 @@ public final class InputMethodManager { editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText()); mCurrentInputMethodSession.invalidateInput(editorInfo, mServedInputConnection, sessionId); + forAccessibilitySessions(wrapper -> wrapper.invalidateInput(editorInfo, + mServedInputConnection, sessionId)); } } @@ -2080,6 +2193,8 @@ public final class InputMethodManager { if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; + mInitialSelStart = mCursorSelStart; + mInitialSelEnd = mCursorSelEnd; mCursorCandStart = -1; mCursorCandEnd = -1; mCursorRect.setEmpty(); @@ -2128,6 +2243,17 @@ public final class InputMethodManager { mBindSequence = res.sequence; mCurMethod = res.method; // for @UnsupportedAppUsage mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method); + mAccessibilityInputMethodSession.clear(); + if (res.accessibilitySessions != null) { + for (int i = 0; i < res.accessibilitySessions.size(); i++) { + InputMethodSessionWrapper wrapper = InputMethodSessionWrapper.createOrNull( + res.accessibilitySessions.valueAt(i)); + if (wrapper != null) { + mAccessibilityInputMethodSession.append( + res.accessibilitySessions.keyAt(i), wrapper); + } + } + } mCurId = res.id; } else if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); @@ -2137,8 +2263,10 @@ public final class InputMethodManager { mRestartOnNextWindowFocus = true; break; } - if (mCurrentInputMethodSession != null && mCompletions != null) { - mCurrentInputMethodSession.displayCompletions(mCompletions); + if (mCompletions != null) { + if (mCurrentInputMethodSession != null) { + mCurrentInputMethodSession.displayCompletions(mCompletions); + } } } @@ -2369,6 +2497,8 @@ public final class InputMethodManager { mCursorCandEnd = candidatesEnd; mCurrentInputMethodSession.updateSelection( oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); + forAccessibilitySessions(wrapper -> wrapper.updateSelection(oldSelStart, + oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd)); } } } @@ -3233,6 +3363,11 @@ public final class InputMethodManager { } else { p.println(" mCurMethod= null"); } + for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) { + p.println(" mAccessibilityInputMethodSession(" + + mAccessibilityInputMethodSession.keyAt(i) + ")=" + + mAccessibilityInputMethodSession.valueAt(i)); + } p.println(" mCurRootView=" + mCurRootView); p.println(" mServedView=" + getServedViewLocked()); p.println(" mNextServedView=" + getNextServedViewLocked()); @@ -3377,4 +3512,10 @@ public final class InputMethodManager { } } } + + private void forAccessibilitySessions(Consumer<InputMethodSessionWrapper> consumer) { + for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) { + consumer.accept(mAccessibilityInputMethodSession.valueAt(i)); + } + } } diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java index 13d44daa5a48..edcbce9a2774 100644 --- a/core/java/android/view/textservice/SpellCheckerInfo.java +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -124,6 +124,7 @@ public final class SpellCheckerInfo implements Parcelable { .SpellChecker_Subtype_subtypeExtraValue), a.getInt(com.android.internal.R.styleable .SpellChecker_Subtype_subtypeId, 0)); + a.recycle(); mSubtypes.add(subtype); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9efa583a894a..c0c7641775f1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12295,7 +12295,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener EXTRA_DATA_RENDERING_INFO_KEY, EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY )); - info.setTextSelectable(isTextSelectable()); + info.setTextSelectable(isTextSelectable() || isTextEditable()); } else { info.setAvailableExtraData(Arrays.asList( EXTRA_DATA_RENDERING_INFO_KEY diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/window/BackEvent.aidl index 4dc6466c7365..821f1fa9830f 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl +++ b/core/java/android/window/BackEvent.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. @@ -14,6 +14,9 @@ * limitations under the License. */ -package android.app.ambientcontext; +package android.window; -parcelable AmbientContextEventResponse;
\ No newline at end of file +/** + * @hide + */ +parcelable BackEvent; diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java new file mode 100644 index 000000000000..14985c987f97 --- /dev/null +++ b/core/java/android/window/BackEvent.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2022 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 android.window; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.RemoteAnimationTarget; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents an event that is sent out by the system during back navigation gesture. + * Holds information about the touch event, swipe direction and overall progress of the gesture + * interaction. + * + * @hide + */ +public class BackEvent implements Parcelable { + /** Indicates that the edge swipe starts from the left edge of the screen */ + public static final int EDGE_LEFT = 0; + /** Indicates that the edge swipe starts from the right edge of the screen */ + public static final int EDGE_RIGHT = 1; + + @IntDef({ + EDGE_LEFT, + EDGE_RIGHT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SwipeEdge{} + + private final int mTouchX; + private final int mTouchY; + private final float mProgress; + + @SwipeEdge + private final int mSwipeEdge; + @Nullable + private final RemoteAnimationTarget mDepartingAnimationTarget; + + /** + * Creates a new {@link BackEvent} instance. + * + * @param touchX Absolute X location of the touch point. + * @param touchY Absolute Y location of the touch point. + * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param swipeEdge Indicates which edge the swipe starts from. + * @param departingAnimationTarget The remote animation target of the departing application + * window. + */ + public BackEvent(int touchX, int touchY, float progress, @SwipeEdge int swipeEdge, + @Nullable RemoteAnimationTarget departingAnimationTarget) { + mTouchX = touchX; + mTouchY = touchY; + mProgress = progress; + mSwipeEdge = swipeEdge; + mDepartingAnimationTarget = departingAnimationTarget; + } + + private BackEvent(@NonNull Parcel in) { + mTouchX = in.readInt(); + mTouchY = in.readInt(); + mProgress = in.readFloat(); + mSwipeEdge = in.readInt(); + mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); + } + + public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() { + @Override + public BackEvent createFromParcel(Parcel in) { + return new BackEvent(in); + } + + @Override + public BackEvent[] newArray(int size) { + return new BackEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mTouchX); + dest.writeInt(mTouchY); + dest.writeFloat(mProgress); + dest.writeInt(mSwipeEdge); + dest.writeTypedObject(mDepartingAnimationTarget, flags); + } + + /** + * Returns a value between 0 and 1 on how far along the back gesture is. + */ + public float getProgress() { + return mProgress; + } + + /** + * Returns the absolute X location of the touch point. + */ + public int getTouchX() { + return mTouchX; + } + + /** + * Returns the absolute Y location of the touch point. + */ + public int getTouchY() { + return mTouchY; + } + + /** + * Returns the screen edge that the swipe starts from. + */ + public int getSwipeEdge() { + return mSwipeEdge; + } + + /** + * Returns the {@link RemoteAnimationTarget} of the top departing application window, + * or {@code null} if the top window should not be moved for the current type of back + * destination. + */ + @Nullable + public RemoteAnimationTarget getDepartingAnimationTarget() { + return mDepartingAnimationTarget; + } + + @Override + public String toString() { + return "BackEvent{" + + "mTouchX=" + mTouchX + + ", mTouchY=" + mTouchY + + ", mProgress=" + mProgress + + ", mSwipeEdge" + mSwipeEdge + + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + + "}"; + } +} diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl index a42863c3126f..47796de11dd5 100644 --- a/core/java/android/window/IOnBackInvokedCallback.aidl +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -17,6 +17,8 @@ package android.window; +import android.window.BackEvent; + /** * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager * and called from back handling process when back is invoked. @@ -38,7 +40,7 @@ oneway interface IOnBackInvokedCallback { * @param touchY Absolute Y location of the touch point. * @param progress Value between 0 and 1 on how far along the back gesture is. */ - void onBackProgressed(int touchX, int touchY, float progress); + void onBackProgressed(in BackEvent backEvent); /** * Called when a back gesture or back button press has been cancelled. diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index b90e628da714..34a34180b227 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -493,7 +493,7 @@ public final class SplashScreenView extends FrameLayout { Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); } - mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage); + mSurfaceView.setChildSurfacePackage(mSurfacePackage); } void initIconAnimation(Drawable iconDrawable, long duration) { diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index d37d3b42872f..03de4796ed74 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -55,10 +55,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties .getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - /** The currently most prioritized callback. */ - @Nullable - private OnBackInvokedCallbackWrapper mTopCallback; - /** Convenience hashmap to quickly decide if a callback has been added. */ private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); /** Holds all callbacks by priorities. */ @@ -72,8 +68,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) { mWindowSession = windowSession; mWindow = window; - if (mTopCallback != null) { - setTopOnBackInvokedCallback(mTopCallback); + if (!mAllCallbacks.isEmpty()) { + setTopOnBackInvokedCallback(getTopCallback()); } } @@ -81,6 +77,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void detachFromWindow() { mWindow = null; mWindowSession = null; + clear(); } // TODO: Take an Executor for the callback to run on. @@ -110,11 +107,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mOnBackInvokedCallbacks.get(prevPriority).remove(callback); } + OnBackInvokedCallback previousTopCallback = getTopCallback(); callbacks.add(callback); mAllCallbacks.put(callback, priority); - if (mTopCallback == null || (mTopCallback.getCallback() != callback - && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) { - setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority)); + if (previousTopCallback == null + || (previousTopCallback != callback + && mAllCallbacks.get(previousTopCallback) <= priority)) { + setTopOnBackInvokedCallback(callback); } } @@ -126,11 +125,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } return; } + OnBackInvokedCallback previousTopCallback = getTopCallback(); Integer priority = mAllCallbacks.get(callback); - mOnBackInvokedCallbacks.get(priority).remove(callback); + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + callbacks.remove(callback); + if (callbacks.isEmpty()) { + mOnBackInvokedCallbacks.remove(priority); + } mAllCallbacks.remove(callback); - if (mTopCallback != null && mTopCallback.getCallback() == callback) { - findAndSetTopOnBackInvokedCallback(); + // Re-populate the top callback to WM if the removed callback was previously the top one. + if (previousTopCallback == callback) { + setTopOnBackInvokedCallback(getTopCallback()); } } @@ -141,41 +146,26 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Clears all registered callbacks on the instance. */ public void clear() { - mAllCallbacks.clear(); - mTopCallback = null; - mOnBackInvokedCallbacks.clear(); - } - - /** - * Iterates through all callbacks to find the most prioritized one and pushes it to - * window manager. - */ - private void findAndSetTopOnBackInvokedCallback() { - if (mAllCallbacks.isEmpty()) { + if (!mAllCallbacks.isEmpty()) { + // Clear binder references in WM. setTopOnBackInvokedCallback(null); - return; } - - for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) { - ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); - if (!callbacks.isEmpty()) { - OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper( - callbacks.get(callbacks.size() - 1), priority); - setTopOnBackInvokedCallback(callback); - return; - } - } - setTopOnBackInvokedCallback(null); + mAllCallbacks.clear(); + mOnBackInvokedCallbacks.clear(); } - // Pushes the top priority callback to window manager. - private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) { - mTopCallback = callback; + private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) { if (mWindowSession == null || mWindow == null) { return; } try { - mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback); + if (callback == null) { + mWindowSession.setOnBackInvokedCallback(mWindow, null); + } else { + int priority = mAllCallbacks.get(callback); + mWindowSession.setOnBackInvokedCallback( + mWindow, new OnBackInvokedCallbackWrapper(callback, priority)); + } } catch (RemoteException e) { Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e); } @@ -202,9 +192,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } @Override - public void onBackProgressed(int touchX, int touchY, float progress) + public void onBackProgressed(BackEvent backEvent) throws RemoteException { - Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress)); + Handler.getMain().post(() -> mCallback.onBackProgressed(backEvent)); } @Override @@ -220,7 +210,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public OnBackInvokedCallback getTopCallback() { - return mTopCallback == null ? null : mTopCallback.getCallback(); + if (mAllCallbacks.isEmpty()) { + return null; + } + for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) { + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + if (!callbacks.isEmpty()) { + return callbacks.get(callbacks.size() - 1); + } + } + return null; } /** diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java index 1357bac30667..e83840177a73 100644 --- a/core/java/com/android/internal/inputmethod/InputBindResult.java +++ b/core/java/com/android/internal/inputmethod/InputBindResult.java @@ -25,6 +25,7 @@ import android.content.ServiceConnection; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.util.SparseArray; import android.view.InputChannel; import com.android.internal.view.IInputMethodSession; @@ -53,7 +54,8 @@ public final class InputBindResult implements Parcelable { ResultCode.ERROR_NOT_IME_TARGET_WINDOW, ResultCode.ERROR_NO_EDITOR, ResultCode.ERROR_DISPLAY_ID_MISMATCH, - ResultCode.ERROR_INVALID_DISPLAY_ID + ResultCode.ERROR_INVALID_DISPLAY_ID, + ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION }) public @interface ResultCode { /** @@ -168,6 +170,7 @@ public final class InputBindResult implements Parcelable { * display. */ int ERROR_INVALID_DISPLAY_ID = 15; + int SUCCESS_WITH_ACCESSIBILITY_SESSION = 16; } @ResultCode @@ -179,6 +182,11 @@ public final class InputBindResult implements Parcelable { public final IInputMethodSession method; /** + * The accessibility services. + */ + public SparseArray<IInputMethodSession> accessibilitySessions; + + /** * The input channel used to send input events to this IME. */ public final InputChannel channel; @@ -204,6 +212,8 @@ public final class InputBindResult implements Parcelable { * * @param result A result code defined in {@link ResultCode}. * @param method {@link IInputMethodSession} to interact with the IME. + * @param accessibilitySessions {@link IInputMethodSession} to interact with accessibility + * services. * @param channel {@link InputChannel} to forward input events to the IME. * @param id The {@link String} representations of the IME, which is the same as * {@link android.view.inputmethod.InputMethodInfo#getId()} and @@ -213,10 +223,12 @@ public final class InputBindResult implements Parcelable { * {@code suppressesSpellChecker="true"}. */ public InputBindResult(@ResultCode int result, - IInputMethodSession method, InputChannel channel, String id, int sequence, + IInputMethodSession method, SparseArray<IInputMethodSession> accessibilitySessions, + InputChannel channel, String id, int sequence, boolean isInputMethodSuppressingSpellChecker) { this.result = result; this.method = method; + this.accessibilitySessions = accessibilitySessions; this.channel = channel; this.id = id; this.sequence = sequence; @@ -226,6 +238,19 @@ public final class InputBindResult implements Parcelable { private InputBindResult(Parcel source) { result = source.readInt(); method = IInputMethodSession.Stub.asInterface(source.readStrongBinder()); + int n = source.readInt(); + if (n < 0) { + accessibilitySessions = null; + } else { + accessibilitySessions = new SparseArray<>(n); + while (n > 0) { + int key = source.readInt(); + IInputMethodSession value = + IInputMethodSession.Stub.asInterface(source.readStrongBinder()); + accessibilitySessions.append(key, value); + n--; + } + } if (source.readInt() != 0) { channel = InputChannel.CREATOR.createFromParcel(source); } else { @@ -254,6 +279,18 @@ public final class InputBindResult implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(result); dest.writeStrongInterface(method); + if (accessibilitySessions == null) { + dest.writeInt(-1); + } else { + int n = accessibilitySessions.size(); + dest.writeInt(n); + int i = 0; + while (i < n) { + dest.writeInt(accessibilitySessions.keyAt(i)); + dest.writeStrongInterface(accessibilitySessions.valueAt(i)); + i++; + } + } if (channel != null) { dest.writeInt(1); channel.writeToParcel(dest, flags); @@ -329,7 +366,7 @@ public final class InputBindResult implements Parcelable { } private static InputBindResult error(@ResultCode int result) { - return new InputBindResult(result, null, null, null, -1, false); + return new InputBindResult(result, null, null, null, null, -1, false); } /** diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index bf094dbd8f1e..d6697684f79e 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -64,6 +64,8 @@ public final class InputMethodDebug { return "DEACTIVATED_BY_IMMS"; case StartInputReason.SESSION_CREATED_BY_IME: return "SESSION_CREATED_BY_IME"; + case StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS: + return "BOUND_ACCESSIBILITY_SESSION_TO_IMMS"; default: return "Unknown=" + reason; } @@ -91,6 +93,8 @@ public final class InputMethodDebug { return "SWITCH_IME_FAILED"; case UnbindReason.SWITCH_USER: return "SWITCH_USER"; + case UnbindReason.ACCESSIBILITY_SERVICE_DISABLED: + return "ACCESSIBILITY_SERVICE_DISABLED"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/StartInputReason.java b/core/java/com/android/internal/inputmethod/StartInputReason.java index 2ba708dd9312..1263466703ac 100644 --- a/core/java/com/android/internal/inputmethod/StartInputReason.java +++ b/core/java/com/android/internal/inputmethod/StartInputReason.java @@ -38,7 +38,9 @@ import java.lang.annotation.Retention; StartInputReason.UNBOUND_FROM_IMMS, StartInputReason.ACTIVATED_BY_IMMS, StartInputReason.DEACTIVATED_BY_IMMS, - StartInputReason.SESSION_CREATED_BY_IME}) + StartInputReason.SESSION_CREATED_BY_IME, + StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY, + StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS}) public @interface StartInputReason { /** * Reason is not specified. @@ -96,4 +98,14 @@ public @interface StartInputReason { * {@link com.android.internal.view.IInputSessionCallback#sessionCreated}. */ int SESSION_CREATED_BY_IME = 10; + /** + * {@link android.accessibilityservice.AccessibilityService} is responding to + * {@link com.android.internal.view.IInputSessionWithIdCallback#sessionCreated}. + */ + int SESSION_CREATED_BY_ACCESSIBILITY = 11; + /** + * {@link android.view.inputmethod.InputMethodManager} is responding to + * {@link com.android.internal.view.IInputMethodClient#onBindAccessibilityService(InputBindResult, int)}. + */ + int BOUND_ACCESSIBILITY_SESSION_TO_IMMS = 12; } diff --git a/core/java/com/android/internal/inputmethod/UnbindReason.java b/core/java/com/android/internal/inputmethod/UnbindReason.java index f0f18f11abe7..e9266251371e 100644 --- a/core/java/com/android/internal/inputmethod/UnbindReason.java +++ b/core/java/com/android/internal/inputmethod/UnbindReason.java @@ -34,7 +34,9 @@ import java.lang.annotation.Retention; UnbindReason.DISCONNECT_IME, UnbindReason.NO_IME, UnbindReason.SWITCH_IME_FAILED, - UnbindReason.SWITCH_USER}) + UnbindReason.SWITCH_USER, + UnbindReason.ACCESSIBILITY_SERVICE_DISABLED +}) public @interface UnbindReason { /** * Reason is not specified. @@ -66,4 +68,5 @@ public @interface UnbindReason { * user's active IME. */ int SWITCH_USER = 6; + int ACCESSIBILITY_SERVICE_DISABLED = 7; } diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java index d8e89b4c2637..888f830960dd 100644 --- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java +++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java @@ -22,10 +22,6 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.UserHandle; -import android.util.SparseArray; - -import java.util.List; /** * Estimates power consumed by the ambient display @@ -67,29 +63,6 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { powerMah, powerModel); } - /** - * Ambient display power is the additional power the screen takes while in ambient display/ - * screen doze/ always-on display (interchangeable terms) mode. Ambient display power should - * be hidden {@link BatteryStatsHelper#shouldHideSipper(BatterySipper)}, but should not be - * included in smearing {@link BatteryStatsHelper#removeHiddenBatterySippers(List)}. - */ - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(); - final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType); - final int powerModel = getPowerModel(measuredEnergyUC); - final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs, - measuredEnergyUC); - if (powerMah > 0) { - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0); - bs.usagePowerMah = powerMah; - bs.usageTimeMs = durationMs; - bs.sumPower(); - sippers.add(bs); - } - } - private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) { return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000; } diff --git a/core/java/com/android/internal/os/BatteryChargeCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java index 71a1463370e5..912ec8f6a6ba 100644 --- a/core/java/com/android/internal/os/BatteryChargeCalculator.java +++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java @@ -20,10 +20,6 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.UserHandle; -import android.util.SparseArray; - -import java.util.List; /** * Estimates the battery discharge amounts. @@ -81,10 +77,4 @@ public class BatteryChargeCalculator extends PowerCalculator { BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) .setConsumedPower(dischargeMah); } - - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - // Not implemented. The computation is done by BatteryStatsHelper - } } diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java deleted file mode 100644 index dfd561a8cc30..000000000000 --- a/core/java/com/android/internal/os/BatterySipper.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2009 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.internal.os; - -import android.compat.annotation.UnsupportedAppUsage; -import android.os.BatteryStats.Uid; -import android.os.Build; - -import java.util.List; - -/** - * Contains power usage of an application, system service, or hardware type. - * - * @deprecated Please use BatteryStatsManager.getBatteryUsageStats instead. - */ -@Deprecated -public class BatterySipper implements Comparable<BatterySipper> { - @UnsupportedAppUsage - public int userId; - @UnsupportedAppUsage - public Uid uidObj; - @UnsupportedAppUsage - public DrainType drainType; - - /** - * Smeared power from screen usage. - * We split the screen usage power and smear them among apps, based on activity time. - * The actual screen usage power may be measured or estimated, affecting the granularity and - * accuracy of the smearing, but the smearing algorithm is essentially the same. - */ - public double screenPowerMah; - - /** - * Smeared power using proportional method. - * - * we smear power usage from hidden sippers to all apps proportionally.(except for screen usage) - * - * @see BatteryStatsHelper#shouldHideSipper(BatterySipper) - * @see BatteryStatsHelper#removeHiddenBatterySippers(List) - */ - public double proportionalSmearMah; - - /** - * Total power that adding the smeared power. - * - * @see #sumPower() - */ - public double totalSmearedPowerMah; - - /** - * Total power before smearing - */ - @UnsupportedAppUsage - public double totalPowerMah; - - /** - * Whether we should hide this sipper - * - * @see BatteryStatsHelper#shouldHideSipper(BatterySipper) - */ - public boolean shouldHide; - - /** - * Generic usage time in milliseconds. - */ - @UnsupportedAppUsage - public long usageTimeMs; - - /** - * Generic power usage in mAh. - */ - public double usagePowerMah; - - // Subsystem usage times. - public long audioTimeMs; - public long bluetoothRunningTimeMs; - public long cameraTimeMs; - @UnsupportedAppUsage - public long cpuFgTimeMs; - @UnsupportedAppUsage - public long cpuTimeMs; - public long flashlightTimeMs; - @UnsupportedAppUsage - public long gpsTimeMs; - public long videoTimeMs; - @UnsupportedAppUsage - public long wakeLockTimeMs; - @UnsupportedAppUsage - public long wifiRunningTimeMs; - - public long mobileRxPackets; - public long mobileTxPackets; - public long mobileActive; - public int mobileActiveCount; - public double mobilemspp; // milliseconds per packet - public long wifiRxPackets; - public long wifiTxPackets; - public long mobileRxBytes; - public long mobileTxBytes; - public long wifiRxBytes; - public long wifiTxBytes; - public long btRxBytes; - public long btTxBytes; - public double percent; - public double noCoveragePercent; - @UnsupportedAppUsage - public String[] mPackages; - @UnsupportedAppUsage - public String packageWithHighestDrain; - - // Measured in mAh (milli-ampere per hour). - // These are included when summed. - public double audioPowerMah; - public double bluetoothPowerMah; - public double cameraPowerMah; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public double cpuPowerMah; - public double flashlightPowerMah; - public double gpsPowerMah; - public double mobileRadioPowerMah; - public double sensorPowerMah; - public double videoPowerMah; - public double wakeLockPowerMah; - public double wifiPowerMah; - public double systemServiceCpuPowerMah; - public double[] customMeasuredPowerMah; - - // Power that is re-attributed to other sippers. For example, for System Server - // this represents the power attributed to apps requesting system services. - // The value should be negative or zero. - public double powerReattributedToOtherSippersMah; - - // Do not include this sipper in results because it is included - // in an aggregate sipper. - public boolean isAggregated; - - // **************** - // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto) - // so the ordinal values (and therefore the order) must never change. - // **************** - @UnsupportedAppUsage(implicitMember = - "values()[Lcom/android/internal/os/BatterySipper$DrainType;") - public enum DrainType { - AMBIENT_DISPLAY, - @UnsupportedAppUsage - APP, - BLUETOOTH, - CAMERA, - CELL, - FLASHLIGHT, - IDLE, - MEMORY, - OVERCOUNTED, - PHONE, - SCREEN, - UNACCOUNTED, - USER, - WIFI, - } - - @UnsupportedAppUsage - public BatterySipper(DrainType drainType, Uid uid, double value) { - this.totalPowerMah = value; - this.drainType = drainType; - uidObj = uid; - } - - public void computeMobilemspp() { - long packets = mobileRxPackets + mobileTxPackets; - mobilemspp = packets > 0 ? (mobileActive / (double) packets) : 0; - } - - @Override - public int compareTo(BatterySipper other) { - // Over-counted always goes to the bottom. - if (drainType != other.drainType) { - if (drainType == DrainType.OVERCOUNTED) { - // This is "larger" - return 1; - } else if (other.drainType == DrainType.OVERCOUNTED) { - return -1; - } - } - // Return the flipped value because we want the items in descending order - return Double.compare(other.totalPowerMah, totalPowerMah); - } - - /** - * Gets a list of packages associated with the current user - */ - @UnsupportedAppUsage - public String[] getPackages() { - return mPackages; - } - - @UnsupportedAppUsage - public int getUid() { - // Bail out if the current sipper is not an App sipper. - if (uidObj == null) { - return 0; - } - return uidObj.getUid(); - } - - /** - * Add stats from other to this BatterySipper. - */ - @UnsupportedAppUsage - public void add(BatterySipper other) { - totalPowerMah += other.totalPowerMah; - usageTimeMs += other.usageTimeMs; - usagePowerMah += other.usagePowerMah; - audioTimeMs += other.audioTimeMs; - cpuTimeMs += other.cpuTimeMs; - gpsTimeMs += other.gpsTimeMs; - wifiRunningTimeMs += other.wifiRunningTimeMs; - cpuFgTimeMs += other.cpuFgTimeMs; - videoTimeMs += other.videoTimeMs; - wakeLockTimeMs += other.wakeLockTimeMs; - cameraTimeMs += other.cameraTimeMs; - flashlightTimeMs += other.flashlightTimeMs; - bluetoothRunningTimeMs += other.bluetoothRunningTimeMs; - mobileRxPackets += other.mobileRxPackets; - mobileTxPackets += other.mobileTxPackets; - mobileActive += other.mobileActive; - mobileActiveCount += other.mobileActiveCount; - wifiRxPackets += other.wifiRxPackets; - wifiTxPackets += other.wifiTxPackets; - mobileRxBytes += other.mobileRxBytes; - mobileTxBytes += other.mobileTxBytes; - wifiRxBytes += other.wifiRxBytes; - wifiTxBytes += other.wifiTxBytes; - btRxBytes += other.btRxBytes; - btTxBytes += other.btTxBytes; - audioPowerMah += other.audioPowerMah; - wifiPowerMah += other.wifiPowerMah; - gpsPowerMah += other.gpsPowerMah; - cpuPowerMah += other.cpuPowerMah; - sensorPowerMah += other.sensorPowerMah; - mobileRadioPowerMah += other.mobileRadioPowerMah; - wakeLockPowerMah += other.wakeLockPowerMah; - cameraPowerMah += other.cameraPowerMah; - flashlightPowerMah += other.flashlightPowerMah; - bluetoothPowerMah += other.bluetoothPowerMah; - screenPowerMah += other.screenPowerMah; - videoPowerMah += other.videoPowerMah; - proportionalSmearMah += other.proportionalSmearMah; - totalSmearedPowerMah += other.totalSmearedPowerMah; - systemServiceCpuPowerMah += other.systemServiceCpuPowerMah; - if (other.customMeasuredPowerMah != null) { - if (customMeasuredPowerMah == null) { - customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length]; - } - if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) { - // This should always be true. - for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) { - customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx]; - } - } - } - powerReattributedToOtherSippersMah += other.powerReattributedToOtherSippersMah; - } - - /** - * Sum all the powers and store the value into `value`. - * Also sum the {@code smearedTotalPowerMah} by adding smeared powerMah. - * - * @return the sum of all the power in this BatterySipper. - */ - public double sumPower() { - totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah + - sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah + - flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah - + systemServiceCpuPowerMah; - if (customMeasuredPowerMah != null) { - for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) { - totalPowerMah += customMeasuredPowerMah[idx]; - } - } - - // powerAttributedToOtherSippersMah is negative or zero - totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah; - - totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah; - - return totalPowerMah; - } -} diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java deleted file mode 100644 index 4515a09df736..000000000000 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ /dev/null @@ -1,703 +0,0 @@ -/* - * Copyright (C) 2009 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.internal.os; - -import android.compat.annotation.UnsupportedAppUsage; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.hardware.SensorManager; -import android.os.BatteryStats; -import android.os.BatteryStats.Uid; -import android.os.Build; -import android.os.Bundle; -import android.os.MemoryFile; -import android.os.Parcel; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.RemoteException; -import android.os.SELinux; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.telephony.TelephonyManager; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.IBatteryStats; -import com.android.internal.os.BatterySipper.DrainType; -import com.android.internal.util.ArrayUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A helper class for retrieving the power usage information for all applications and services. - * - * The caller must initialize this class as soon as activity object is ready to use (for example, in - * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy(). - * - * @deprecated Please use BatteryStatsManager.getBatteryUsageStats instead. - */ -@Deprecated -public class BatteryStatsHelper { - private static final boolean DEBUG = false; - - private static final String TAG = BatteryStatsHelper.class.getSimpleName(); - - private static BatteryStats sStatsXfer; - private static Intent sBatteryBroadcastXfer; - private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>(); - - final private Context mContext; - final private boolean mCollectBatteryBroadcast; - final private boolean mWifiOnly; - - private List<PowerCalculator> mPowerCalculators; - - @UnsupportedAppUsage - private IBatteryStats mBatteryInfo; - private BatteryStats mStats; - private Intent mBatteryBroadcast; - @UnsupportedAppUsage - private PowerProfile mPowerProfile; - - private String[] mSystemPackageArray; - private String[] mServicepackageArray; - private PackageManager mPackageManager; - - /** - * List of apps using power. - */ - @UnsupportedAppUsage - private final List<BatterySipper> mUsageList = new ArrayList<>(); - - private final List<BatterySipper> mMobilemsppList = new ArrayList<>(); - - private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; - - long mRawRealtimeUs; - long mRawUptimeUs; - long mBatteryRealtimeUs; - long mBatteryUptimeUs; - long mBatteryTimeRemainingUs; - long mChargeTimeRemainingUs; - - private long mStatsPeriod = 0; - - // The largest entry by power. - private double mMaxPower = 1; - - // The largest real entry by power (not undercounted or overcounted). - private double mMaxRealPower = 1; - - // Total computed power. - private double mComputedPower; - private double mTotalPower; - private double mMinDrainedPower; - private double mMaxDrainedPower; - - public static boolean checkWifiOnly(Context context) { - final TelephonyManager tm = context.getSystemService(TelephonyManager.class); - if (tm == null) { - return false; - } - return !tm.isDataCapable(); - } - - @UnsupportedAppUsage - public BatteryStatsHelper(Context context) { - this(context, true); - } - - @UnsupportedAppUsage - public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) { - this(context, collectBatteryBroadcast, checkWifiOnly(context)); - } - - @UnsupportedAppUsage - public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) { - mContext = context; - mCollectBatteryBroadcast = collectBatteryBroadcast; - mWifiOnly = wifiOnly; - mPackageManager = context.getPackageManager(); - - final Resources resources = context.getResources(); - mSystemPackageArray = resources.getStringArray( - com.android.internal.R.array.config_batteryPackageTypeSystem); - mServicepackageArray = resources.getStringArray( - com.android.internal.R.array.config_batteryPackageTypeService); - } - - public void storeStatsHistoryInFile(String fname) { - synchronized (sFileXfer) { - File path = makeFilePath(mContext, fname); - sFileXfer.put(path, this.getStats()); - FileOutputStream fout = null; - try { - fout = new FileOutputStream(path); - Parcel hist = Parcel.obtain(); - getStats().writeToParcelWithoutUids(hist, 0); - byte[] histData = hist.marshall(); - fout.write(histData); - } catch (IOException e) { - Log.w(TAG, "Unable to write history to file", e); - } finally { - if (fout != null) { - try { - fout.close(); - } catch (IOException e) { - } - } - } - } - } - - public static BatteryStats statsFromFile(Context context, String fname) { - synchronized (sFileXfer) { - File path = makeFilePath(context, fname); - BatteryStats stats = sFileXfer.get(path); - if (stats != null) { - return stats; - } - FileInputStream fin = null; - try { - fin = new FileInputStream(path); - byte[] data = readFully(fin); - Parcel parcel = Parcel.obtain(); - parcel.unmarshall(data, 0, data.length); - parcel.setDataPosition(0); - return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel); - } catch (IOException e) { - Log.w(TAG, "Unable to read history to file", e); - } finally { - if (fin != null) { - try { - fin.close(); - } catch (IOException e) { - } - } - } - } - return getStats(IBatteryStats.Stub.asInterface( - ServiceManager.getService(BatteryStats.SERVICE_NAME)), true); - } - - @UnsupportedAppUsage - public static void dropFile(Context context, String fname) { - makeFilePath(context, fname).delete(); - } - - private static File makeFilePath(Context context, String fname) { - return new File(context.getFilesDir(), fname); - } - - /** Clears the current stats and forces recreating for future use. */ - @UnsupportedAppUsage - public void clearStats() { - mStats = null; - } - - @UnsupportedAppUsage - public BatteryStats getStats() { - return getStats(true /* updateAll */); - } - - /** Retrieves stats from BatteryService, optionally getting updated numbers */ - public BatteryStats getStats(boolean updateAll) { - if (mStats == null) { - load(updateAll); - } - return mStats; - } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public Intent getBatteryBroadcast() { - if (mBatteryBroadcast == null && mCollectBatteryBroadcast) { - load(); - } - return mBatteryBroadcast; - } - - public PowerProfile getPowerProfile() { - return mPowerProfile; - } - - public void create(BatteryStats stats) { - mPowerProfile = new PowerProfile(mContext); - mStats = stats; - } - - @UnsupportedAppUsage - public void create(Bundle icicle) { - if (icicle != null) { - mStats = sStatsXfer; - mBatteryBroadcast = sBatteryBroadcastXfer; - } - mBatteryInfo = IBatteryStats.Stub.asInterface( - ServiceManager.getService(BatteryStats.SERVICE_NAME)); - mPowerProfile = new PowerProfile(mContext); - } - - @UnsupportedAppUsage - public void storeState() { - sStatsXfer = mStats; - sBatteryBroadcastXfer = mBatteryBroadcast; - } - - /** - * Refreshes the power usage list. - */ - @UnsupportedAppUsage - public void refreshStats(int statsType, int asUser) { - SparseArray<UserHandle> users = new SparseArray<>(1); - users.put(asUser, new UserHandle(asUser)); - refreshStats(statsType, users); - } - - /** - * Refreshes the power usage list. - */ - @UnsupportedAppUsage - public void refreshStats(int statsType, List<UserHandle> asUsers) { - final int n = asUsers.size(); - SparseArray<UserHandle> users = new SparseArray<>(n); - for (int i = 0; i < n; ++i) { - UserHandle userHandle = asUsers.get(i); - users.put(userHandle.getIdentifier(), userHandle); - } - refreshStats(statsType, users); - } - - /** - * Refreshes the power usage list. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) { - refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000, - SystemClock.uptimeMillis() * 1000); - } - - public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs, - long rawUptimeUs) { - if (statsType != BatteryStats.STATS_SINCE_CHARGED) { - Log.w(TAG, "refreshStats called for statsType " + statsType + " but only " - + "STATS_SINCE_CHARGED is supported. Using STATS_SINCE_CHARGED instead."); - } - - // Initialize mStats if necessary. - getStats(); - - mMaxPower = 0; - mMaxRealPower = 0; - mComputedPower = 0; - mTotalPower = 0; - - mUsageList.clear(); - mMobilemsppList.clear(); - - if (mStats == null) { - return; - } - - if (mPowerCalculators == null) { - mPowerCalculators = new ArrayList<>(); - - // Power calculators are applied in the order of registration - mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); - if (!mWifiOnly) { - mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile)); - } - mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new SensorPowerCalculator( - mContext.getSystemService(SensorManager.class))); - mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new MediaPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile)); - mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile)); - mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); - mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile)); - - mPowerCalculators.add(new UserPowerCalculator()); - } - - for (int i = 0, size = mPowerCalculators.size(); i < size; i++) { - mPowerCalculators.get(i).reset(); - } - - mStatsType = statsType; - mRawUptimeUs = rawUptimeUs; - mRawRealtimeUs = rawRealtimeUs; - mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs); - mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs); - mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs); - mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs); - mStatsPeriod = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType); - - if (DEBUG) { - Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs / 1000) + " uptime=" - + (rawUptimeUs / 1000)); - Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtimeUs / 1000) + " uptime=" - + (mBatteryUptimeUs / 1000)); - Log.d(TAG, "Battery type time: realtime=" + (mStatsPeriod / 1000) + " uptime=" - + (mStats.computeBatteryUptime(rawRealtimeUs, mStatsType) / 1000)); - } - mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge() - * mPowerProfile.getBatteryCapacity()) / 100; - mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge() - * mPowerProfile.getBatteryCapacity()) / 100; - - // Create list of (almost all) sippers, calculate their usage, and put them in mUsageList. - processAppUsage(asUsers); - - Collections.sort(mUsageList); - - Collections.sort(mMobilemsppList, - (lhs, rhs) -> Double.compare(rhs.mobilemspp, lhs.mobilemspp)); - - // At this point, we've sorted the list so we are guaranteed the max values are at the top. - // We have only added real powers so far. - if (!mUsageList.isEmpty()) { - mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah; - final int usageListCount = mUsageList.size(); - for (int i = 0; i < usageListCount; i++) { - mComputedPower += mUsageList.get(i).totalPowerMah; - } - } - - if (DEBUG) { - Log.d(TAG, "Accuracy: total computed=" + PowerCalculator.formatCharge(mComputedPower) - + ", min discharge=" + PowerCalculator.formatCharge(mMinDrainedPower) - + ", max discharge=" + PowerCalculator.formatCharge(mMaxDrainedPower)); - } - - mTotalPower = mComputedPower; - if (mStats.getLowDischargeAmountSinceCharge() > 1) { - if (mMinDrainedPower > mComputedPower) { - double amount = mMinDrainedPower - mComputedPower; - mTotalPower = mMinDrainedPower; - BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount); - - // Insert the BatterySipper in its sorted position. - int index = Collections.binarySearch(mUsageList, bs); - if (index < 0) { - index = -(index + 1); - } - mUsageList.add(index, bs); - mMaxPower = Math.max(mMaxPower, amount); - } else if (mMaxDrainedPower < mComputedPower) { - double amount = mComputedPower - mMaxDrainedPower; - - // Insert the BatterySipper in its sorted position. - BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount); - int index = Collections.binarySearch(mUsageList, bs); - if (index < 0) { - index = -(index + 1); - } - mUsageList.add(index, bs); - mMaxPower = Math.max(mMaxPower, amount); - } - } - - // Smear it! - final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList); - final double totalRemainingPower = getTotalPower() - hiddenPowerMah; - if (Math.abs(totalRemainingPower) > 1e-3) { - for (int i = 0, size = mUsageList.size(); i < size; i++) { - final BatterySipper sipper = mUsageList.get(i); - if (!sipper.shouldHide) { - sipper.proportionalSmearMah = hiddenPowerMah - * ((sipper.totalPowerMah + sipper.screenPowerMah) - / totalRemainingPower); - sipper.sumPower(); - } - } - } - } - - private void processAppUsage(SparseArray<UserHandle> asUsers) { - final SparseArray<? extends Uid> uidStats = mStats.getUidStats(); - - final ArrayList<BatterySipper> sippers = new ArrayList<>(uidStats.size()); - - for (int iu = 0, size = uidStats.size(); iu < size; iu++) { - final Uid u = uidStats.valueAt(iu); - sippers.add(new BatterySipper(DrainType.APP, u, 0)); - } - - for (int i = 0, size = mPowerCalculators.size(); i < size; i++) { - final PowerCalculator calculator = mPowerCalculators.get(i); - calculator.calculate(sippers, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType, - asUsers); - } - - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper sipper = sippers.get(i); - final double totalPower = sipper.sumPower(); - if (DEBUG && totalPower != 0) { - Log.d(TAG, String.format("UID %d: total power=%s", sipper.getUid(), - PowerCalculator.formatCharge(totalPower))); - } - - // Add the sipper to the list if it is consuming power. - if (totalPower != 0 || sipper.getUid() == 0) { - if (sipper.drainType == DrainType.APP) { - sipper.computeMobilemspp(); - if (sipper.mobilemspp != 0) { - mMobilemsppList.add(sipper); - } - } - - if (!sipper.isAggregated) { - mUsageList.add(sipper); - } - } - } - } - - @UnsupportedAppUsage - public List<BatterySipper> getUsageList() { - return mUsageList; - } - - public List<BatterySipper> getMobilemsppList() { - return mMobilemsppList; - } - - public long getStatsPeriod() { - return mStatsPeriod; - } - - public int getStatsType() { - return mStatsType; - } - - @UnsupportedAppUsage - public double getMaxPower() { - return mMaxPower; - } - - public double getMaxRealPower() { - return mMaxRealPower; - } - - @UnsupportedAppUsage - public double getTotalPower() { - return mTotalPower; - } - - public double getComputedPower() { - return mComputedPower; - } - - public double getMinDrainedPower() { - return mMinDrainedPower; - } - - public double getMaxDrainedPower() { - return mMaxDrainedPower; - } - - public static byte[] readFully(FileInputStream stream) throws java.io.IOException { - return readFully(stream, stream.available()); - } - - public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException { - int pos = 0; - byte[] data = new byte[avail]; - while (true) { - int amt = stream.read(data, pos, data.length - pos); - //Log.i("foo", "Read " + amt + " bytes at " + pos - // + " of avail " + data.length); - if (amt <= 0) { - //Log.i("foo", "**** FINISHED READING: pos=" + pos - // + " len=" + data.length); - return data; - } - pos += amt; - avail = stream.available(); - if (avail > data.length - pos) { - byte[] newData = new byte[pos + avail]; - System.arraycopy(data, 0, newData, 0, pos); - data = newData; - } - } - } - - /** - * Mark the {@link BatterySipper} that we should hide. - * - * @param sippers sipper list that need to check and remove - * @return the total power of the hidden items of {@link BatterySipper} - * for proportional smearing - */ - public double removeHiddenBatterySippers(List<BatterySipper> sippers) { - double proportionalSmearPowerMah = 0; - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper sipper = sippers.get(i); - sipper.shouldHide = shouldHideSipper(sipper); - if (sipper.shouldHide) { - if (sipper.drainType != DrainType.OVERCOUNTED - && sipper.drainType != DrainType.SCREEN - && sipper.drainType != DrainType.AMBIENT_DISPLAY - && sipper.drainType != DrainType.UNACCOUNTED - && sipper.drainType != DrainType.BLUETOOTH - && sipper.drainType != DrainType.WIFI - && sipper.drainType != DrainType.IDLE) { - // Don't add it if it is overcounted, unaccounted or screen - proportionalSmearPowerMah += sipper.totalPowerMah; - } - } - } - return proportionalSmearPowerMah; - } - - /** - * Check whether we should hide the battery sipper. - */ - public boolean shouldHideSipper(BatterySipper sipper) { - final DrainType drainType = sipper.drainType; - - return drainType == DrainType.IDLE - || drainType == DrainType.CELL - || drainType == DrainType.SCREEN - || drainType == DrainType.AMBIENT_DISPLAY - || drainType == DrainType.UNACCOUNTED - || drainType == DrainType.OVERCOUNTED - || isTypeService(sipper) - || isTypeSystem(sipper); - } - - /** - * Check whether {@code sipper} is type service - */ - public boolean isTypeService(BatterySipper sipper) { - final String[] packages = mPackageManager.getPackagesForUid(sipper.getUid()); - if (packages == null) { - return false; - } - - for (String packageName : packages) { - if (ArrayUtils.contains(mServicepackageArray, packageName)) { - return true; - } - } - - return false; - } - - /** - * Check whether {@code sipper} is type system - */ - public boolean isTypeSystem(BatterySipper sipper) { - final int uid = sipper.uidObj == null ? -1 : sipper.getUid(); - sipper.mPackages = mPackageManager.getPackagesForUid(uid); - // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID - if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) { - return true; - } else if (sipper.mPackages != null) { - for (final String packageName : sipper.mPackages) { - if (ArrayUtils.contains(mSystemPackageArray, packageName)) { - return true; - } - } - } - - return false; - } - - public long convertUsToMs(long timeUs) { - return timeUs / 1000; - } - - public long convertMsToUs(long timeMs) { - return timeMs * 1000; - } - - @VisibleForTesting - public void setPackageManager(PackageManager packageManager) { - mPackageManager = packageManager; - } - - @VisibleForTesting - public void setSystemPackageArray(String[] array) { - mSystemPackageArray = array; - } - - @VisibleForTesting - public void setServicePackageArray(String[] array) { - mServicepackageArray = array; - } - - @UnsupportedAppUsage - private void load() { - load(true); - } - - private void load(boolean updateAll) { - if (mBatteryInfo == null) { - return; - } - mStats = getStats(mBatteryInfo, updateAll); - if (mCollectBatteryBroadcast) { - mBatteryBroadcast = mContext.registerReceiver(null, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - } - } - - private static BatteryStatsImpl getStats(IBatteryStats service, boolean updateAll) { - try { - ParcelFileDescriptor pfd = service.getStatisticsStream(updateAll); - if (pfd != null) { - if (false) { - Log.d(TAG, "selinux context: " - + SELinux.getFileContext(pfd.getFileDescriptor())); - } - try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { - byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor())); - Parcel parcel = Parcel.obtain(); - parcel.unmarshall(data, 0, data.length); - parcel.setDataPosition(0); - BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR - .createFromParcel(parcel); - return stats; - } catch (IOException e) { - Log.w(TAG, "Unable to read statistics stream", e); - } - } - } catch (RemoteException e) { - Log.w(TAG, "RemoteException:", e); - } - return new BatteryStatsImpl(); - } -} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 5ba45c9b8bc3..70b96392b0e5 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -9211,7 +9211,7 @@ public class BatteryStatsImpl extends BatteryStats { * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time * since last marked. Also sets the mark time for both these timers. * - * @see BatteryStatsHelper#getProcessForegroundTimeMs + * @see CpuPowerCalculator * * @param doCalc if true, then calculate the minimum; else don't bother and return 0. Either * way, the mark is set. diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index e4d5fb7a3ede..a1c1917fd83e 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -73,7 +73,7 @@ public class BatteryUsageStatsProvider { mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile)); mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); - if (!BatteryStatsHelper.checkWifiOnly(mContext)) { + if (!BatteryStats.checkWifiOnly(mContext)) { mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile)); } mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java index afa41a763082..2ebf689e2c7f 100644 --- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java +++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java @@ -21,14 +21,11 @@ import android.os.BatteryStats; import android.os.BatteryStats.ControllerActivityCounter; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Process; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import java.util.Arrays; -import java.util.List; public class BluetoothPowerCalculator extends PowerCalculator { private static final String TAG = "BluetoothPowerCalc"; @@ -106,7 +103,7 @@ public class BluetoothPowerCalculator extends PowerCalculator { powerAndDuration.durationMs - powerAndDuration.totalDurationMs); if (DEBUG) { Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs) - + " power=" + formatCharge(powerAndDuration.powerMah)); + + " power=" + BatteryStats.formatCharge(powerAndDuration.powerMah)); } builder.getAggregateBatteryConsumerBuilder( @@ -159,73 +156,6 @@ public class BluetoothPowerCalculator extends PowerCalculator { } } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) { - return; - } - - PowerAndDuration powerAndDuration = new PowerAndDuration(); - - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, statsType, powerAndDuration); - } - } - - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0); - final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(measuredChargeUC); - final ControllerActivityCounter activityCounter = - batteryStats.getBluetoothControllerActivity(); - calculatePowerAndDuration(null, powerModel, measuredChargeUC, activityCounter, false, - powerAndDuration); - - // Subtract what the apps used, but clamp to 0. - final double powerMah = Math.max(0, - powerAndDuration.powerMah - powerAndDuration.totalPowerMah); - final long durationMs = Math.max(0, - powerAndDuration.durationMs - powerAndDuration.totalDurationMs); - if (DEBUG && powerMah != 0) { - Log.d(TAG, "Bluetooth active: time=" + (durationMs) - + " power=" + formatCharge(powerMah)); - } - - bs.bluetoothPowerMah = powerMah; - bs.bluetoothRunningTimeMs = durationMs; - - for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.BLUETOOTH_UID) { - if (DEBUG) Log.d(TAG, "Bluetooth adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); - } - } - if (bs.sumPower() > 0) { - sippers.add(bs); - } - } - - private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - PowerAndDuration powerAndDuration) { - final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(measuredChargeUC); - final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity(); - calculatePowerAndDuration(u, powerModel, measuredChargeUC, activityCounter, - false, powerAndDuration); - - app.bluetoothRunningTimeMs = powerAndDuration.durationMs; - app.bluetoothPowerMah = powerAndDuration.powerMah; - app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType); - app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType); - - powerAndDuration.totalDurationMs += powerAndDuration.durationMs; - powerAndDuration.totalPowerMah += powerAndDuration.powerMah; - } - /** Returns bluetooth power usage based on the best data available. */ private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid, @BatteryConsumer.PowerModel int powerModel, diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java index 7bccab5fc665..d0749e02abdd 100644 --- a/core/java/com/android/internal/os/CameraPowerCalculator.java +++ b/core/java/com/android/internal/os/CameraPowerCalculator.java @@ -69,14 +69,4 @@ public class CameraPowerCalculator extends PowerCalculator { app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah); } - - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - final long durationMs = mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(), - rawRealtimeUs, statsType); - final double powerMah = mPowerEstimator.calculatePower(durationMs); - app.cameraTimeMs = durationMs; - app.cameraPowerMah = powerMah; - } } diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java index 9940a0a6f7d3..1fc2baf040b6 100644 --- a/core/java/com/android/internal/os/CpuPowerCalculator.java +++ b/core/java/com/android/internal/os/CpuPowerCalculator.java @@ -20,13 +20,11 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import java.util.Arrays; -import java.util.List; public class CpuPowerCalculator extends PowerCalculator { private static final String TAG = "CpuPowerCalculator"; @@ -217,29 +215,6 @@ public class CpuPowerCalculator extends PowerCalculator { } } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - Result result = new Result(); - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, statsType, result); - } - } - } - - private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) { - final long consumptionUC = u.getCpuMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - calculatePowerAndDuration(u, powerModel, consumptionUC, statsType, result); - - app.cpuPowerMah = result.powerMah; - app.cpuTimeMs = result.durationMs; - app.cpuFgTimeMs = result.durationFgMs; - app.packageWithHighestDrain = result.packageWithHighestDrain; - } - private void calculatePowerAndDuration(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel, long consumptionUC, int statsType, Result result) { @@ -258,7 +233,7 @@ public class CpuPowerCalculator extends PowerCalculator { if (DEBUG && (durationMs != 0 || powerMah != 0)) { Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + durationMs + " ms power=" - + formatCharge(powerMah)); + + BatteryStats.formatCharge(powerMah)); } // Keep track of the package with highest drain. @@ -325,7 +300,7 @@ public class CpuPowerCalculator extends PowerCalculator { if (DEBUG) { Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster + " clusterTimeMs=" + cpuClusterTimes[cluster] - + " power=" + formatCharge(power)); + + " power=" + BatteryStats.formatCharge(power)); } } } else { diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java index 4cb7ef114094..cbbb52621111 100644 --- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java +++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java @@ -102,16 +102,6 @@ public class CustomMeasuredPowerCalculator extends PowerCalculator { return newTotalPowerMah; } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - updateCustomMeasuredPowerMah(app, u.getCustomConsumerMeasuredBatteryConsumptionUC()); - } - - private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredChargeUC) { - sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredChargeUC); - } - private double[] calculateMeasuredEnergiesMah(long[] measuredChargeUC) { if (measuredChargeUC == null) { return null; diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java index 7d3f9625aa4e..ce3e7b9c7484 100644 --- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java +++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java @@ -66,14 +66,4 @@ public class FlashlightPowerCalculator extends PowerCalculator { app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, durationMs) .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah); } - - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - final long durationMs = mPowerEstimator.calculateDuration(u.getFlashlightTurnedOnTimer(), - rawRealtimeUs, statsType); - final double powerMah = mPowerEstimator.calculatePower(durationMs); - app.flashlightTimeMs = durationMs; - app.flashlightPowerMah = powerMah; - } } diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java index a836ddb444f4..0f783062f3e7 100644 --- a/core/java/com/android/internal/os/GnssPowerCalculator.java +++ b/core/java/com/android/internal/os/GnssPowerCalculator.java @@ -21,11 +21,8 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.SparseArray; -import java.util.List; - /** * Estimates the amount of power consumed by the GNSS (e.g. GPS). */ @@ -100,41 +97,6 @@ public class GnssPowerCalculator extends PowerCalculator { return powerMah; } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs, statsType); - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - final long consumptionUC = - app.uidObj.getGnssMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - calculateApp(app, app.uidObj, powerModel, rawRealtimeUs, averageGnssPowerMa, - consumptionUC); - } - } - } - - private void calculateApp(BatterySipper app, BatteryStats.Uid u, - @BatteryConsumer.PowerModel int powerModel, long rawRealtimeUs, - double averageGnssPowerMa, long measuredChargeUC) { - final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - - final double powerMah; - switch (powerModel) { - case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: - powerMah = uCtoMah(measuredChargeUC); - break; - case BatteryConsumer.POWER_MODEL_POWER_PROFILE: - default: - powerMah = computePower(durationMs, averageGnssPowerMa); - } - - app.gpsTimeMs = durationMs; - app.gpsPowerMah = powerMah; - } - private long computeDuration(BatteryStats.Uid u, long rawRealtimeUs, int statsType) { final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); final BatteryStats.Uid.Sensor sensor = sensorStats.get(BatteryStats.Uid.Sensor.GPS); diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java index 9491b3b90948..5b2052e9edaa 100644 --- a/core/java/com/android/internal/os/IdlePowerCalculator.java +++ b/core/java/com/android/internal/os/IdlePowerCalculator.java @@ -20,11 +20,7 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.UserHandle; import android.util.Log; -import android.util.SparseArray; - -import java.util.List; /** * Estimates the amount of power consumed when the device is idle. @@ -64,20 +60,6 @@ public class IdlePowerCalculator extends PowerCalculator { } } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs, statsType); - - if (mPowerMah != 0) { - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.IDLE, null, 0); - bs.usagePowerMah = mPowerMah; - bs.usageTimeMs = mDurationMs; - bs.sumPower(); - sippers.add(bs); - } - } - /** * Calculates the baseline power usage for the device when it is in suspend and idle. * The device is drawing POWER_CPU_SUSPEND power at its lowest power state. @@ -97,9 +79,9 @@ public class IdlePowerCalculator extends PowerCalculator { mPowerMah = suspendPowerMah + idlePowerMah; if (DEBUG && mPowerMah != 0) { Log.d(TAG, "Suspend: time=" + (batteryRealtimeUs / 1000) - + " power=" + formatCharge(suspendPowerMah)); + + " power=" + BatteryStats.formatCharge(suspendPowerMah)); Log.d(TAG, "Idle: time=" + (batteryUptimeUs / 1000) - + " power=" + formatCharge(idlePowerMah)); + + " power=" + BatteryStats.formatCharge(idlePowerMah)); } mDurationMs = batteryRealtimeUs / 1000; } diff --git a/core/java/com/android/internal/os/MediaPowerCalculator.java b/core/java/com/android/internal/os/MediaPowerCalculator.java deleted file mode 100644 index fff96dacf84b..000000000000 --- a/core/java/com/android/internal/os/MediaPowerCalculator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.os; - -import android.os.BatteryConsumer; -import android.os.BatteryStats; - -/** - * A {@link PowerCalculator} to calculate power consumed by audio and video hardware. - * - * Also see {@link PowerProfile#POWER_AUDIO} and {@link PowerProfile#POWER_VIDEO}. - */ -public class MediaPowerCalculator extends PowerCalculator { - private static final int MS_IN_HR = 1000 * 60 * 60; - private final double mAudioAveragePowerMa; - private final double mVideoAveragePowerMa; - - public MediaPowerCalculator(PowerProfile profile) { - mAudioAveragePowerMa = profile.getAveragePower(PowerProfile.POWER_AUDIO); - mVideoAveragePowerMa = profile.getAveragePower(PowerProfile.POWER_VIDEO); - } - - @Override - public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) { - return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO - || powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO; - } - - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - // Calculate audio power usage, an estimate based on the average power routed to different - // components like speaker, bluetooth, usb-c, earphone, etc. - final BatteryStats.Timer audioTimer = u.getAudioTurnedOnTimer(); - if (audioTimer == null) { - app.audioTimeMs = 0; - app.audioPowerMah = 0; - } else { - final long totalTime = audioTimer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; - app.audioTimeMs = totalTime; - app.audioPowerMah = (totalTime * mAudioAveragePowerMa) / MS_IN_HR; - } - - // Calculate video power usage. - final BatteryStats.Timer videoTimer = u.getVideoTurnedOnTimer(); - if (videoTimer == null) { - app.videoTimeMs = 0; - app.videoPowerMah = 0; - } else { - final long totalTime = videoTimer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; - app.videoTimeMs = totalTime; - app.videoPowerMah = (totalTime * mVideoAveragePowerMa) / MS_IN_HR; - } - } -} diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java index 0440a588a093..0d3040cc7f92 100644 --- a/core/java/com/android/internal/os/MemoryPowerCalculator.java +++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java @@ -4,11 +4,7 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.UserHandle; import android.util.LongSparseArray; -import android.util.SparseArray; - -import java.util.List; public class MemoryPowerCalculator extends PowerCalculator { public static final String TAG = "MemoryPowerCalculator"; @@ -41,20 +37,6 @@ public class MemoryPowerCalculator extends PowerCalculator { .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY, powerMah); } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType); - final double powerMah = calculatePower(batteryStats, rawRealtimeUs, statsType); - BatterySipper memory = new BatterySipper(BatterySipper.DrainType.MEMORY, null, 0); - memory.usageTimeMs = durationMs; - memory.usagePowerMah = powerMah; - memory.sumPower(); - if (memory.totalPowerMah > 0) { - sippers.add(memory); - } - } - private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) { long usageDurationMs = 0; LongSparseArray<? extends BatteryStats.Timer> timers = batteryStats.getKernelMemoryStats(); diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java index 3a50b73f65d3..f4624de289be 100644 --- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -20,13 +20,10 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.telephony.CellSignalStrength; import android.util.Log; import android.util.SparseArray; -import java.util.List; - public class MobileRadioPowerCalculator extends PowerCalculator { private static final String TAG = "MobRadioPowerCalculator"; private static final boolean DEBUG = PowerCalculator.DEBUG; @@ -170,65 +167,6 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - PowerAndDuration total = new PowerAndDuration(); - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - final BatteryStats.Uid u = app.uidObj; - calculateApp(app, u, statsType, total); - } - } - - BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0); - final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC); - if (total.remainingPowerMah != 0) { - if (total.signalDurationMs != 0) { - radio.noCoveragePercent = - total.noCoverageDurationMs * 100.0 / total.signalDurationMs; - } - radio.mobileActive = total.durationMs; - radio.mobileActiveCount = batteryStats.getMobileRadioActiveUnknownCount(statsType); - radio.mobileRadioPowerMah = total.remainingPowerMah; - radio.sumPower(); - } - if (radio.totalPowerMah > 0) { - sippers.add(radio); - } - } - - private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, - PowerAndDuration total) { - app.mobileActive = calculateDuration(u, statsType); - - final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - app.mobileRadioPowerMah = calculatePower(u, powerModel, app.mobileActive, consumptionUC); - total.totalAppDurationMs += app.mobileActive; - - // Add cost of mobile traffic. - app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, - statsType); - app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, - statsType); - app.mobileActiveCount = u.getMobileRadioActiveCount(statsType); - app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA, - statsType); - app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA, - statsType); - - if (DEBUG && app.mobileRadioPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": mobile packets " - + (app.mobileRxPackets + app.mobileTxPackets) - + " active time " + app.mobileActive - + " power=" + formatCharge(app.mobileRadioPowerMah)); - } - } - private long calculateDuration(BatteryStats.Uid u, int statsType) { return u.getMobileRadioActiveTime(statsType) / 1000; } @@ -262,7 +200,7 @@ public class MobileRadioPowerCalculator extends PowerCalculator { final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i); if (DEBUG && p != 0) { Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" - + formatCharge(p)); + + BatteryStats.formatCharge(p)); } powerMah += p; } @@ -281,8 +219,8 @@ public class MobileRadioPowerCalculator extends PowerCalculator { if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { final double p = calcScanTimePowerMah(scanningTimeMs); if (DEBUG && p != 0) { - Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge( - p)); + Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + + " power=" + BatteryStats.formatCharge(p)); } powerMah += p; diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java index 73103148a945..cb893defab14 100644 --- a/core/java/com/android/internal/os/PhonePowerCalculator.java +++ b/core/java/com/android/internal/os/PhonePowerCalculator.java @@ -20,10 +20,6 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.UserHandle; -import android.util.SparseArray; - -import java.util.List; /** * Estimates power consumed by telephony. @@ -54,18 +50,4 @@ public class PhonePowerCalculator extends PowerCalculator { .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs); } } - - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs, statsType) / 1000; - final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs); - if (phoneOnPower != 0) { - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.PHONE, null, 0); - bs.usagePowerMah = phoneOnPower; - bs.usageTimeMs = phoneOnTimeMs; - bs.sumPower(); - sippers.add(bs); - } - } } diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java index 28c0f5a4c6ec..ec785b8ac502 100644 --- a/core/java/com/android/internal/os/PowerCalculator.java +++ b/core/java/com/android/internal/os/PowerCalculator.java @@ -21,12 +21,9 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.SparseArray; import java.io.PrintWriter; -import java.util.List; -import java.util.Locale; /** * Calculates power use of a device subsystem for an app. @@ -43,34 +40,6 @@ public abstract class PowerCalculator { public abstract boolean isPowerComponentSupported( @BatteryConsumer.PowerComponent int powerComponent); - - /** - * Attributes the total amount of power used by this subsystem to various consumers such - * as apps. - * - * @param sippers A list of battery sippers that contains battery attribution data. - * The calculator may modify the list. - * @param batteryStats The recorded battery stats. - * @param rawRealtimeUs The raw system realtime in microseconds. - * @param rawUptimeUs The raw system uptime in microseconds. - * @param statsType The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this - * can only be {@link BatteryStats#STATS_SINCE_CHARGED}, since - * {@link BatteryStats#STATS_CURRENT} and - * {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated. - * @param asUsers An array of users for which the attribution is requested. It may - * contain {@link UserHandle#USER_ALL} to indicate that the attribution - * should be performed for all users. - */ - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(app, app.uidObj, rawRealtimeUs, rawUptimeUs, statsType); - } - } - } - /** * Attributes the total amount of power used by this subsystem to various consumers such * as apps. @@ -95,21 +64,6 @@ public abstract class PowerCalculator { /** * Calculate the amount of power an app used for this subsystem. - * @param app The BatterySipper that represents the power use of an app. - * @param u The recorded stats for the app. - * @param rawRealtimeUs The raw system realtime in microseconds. - * @param rawUptimeUs The raw system uptime in microseconds. - * @param statsType The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this can - * only be {@link BatteryStats#STATS_SINCE_CHARGED}, since - * {@link BatteryStats#STATS_CURRENT} and - * {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated. - */ - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - } - - /** - * Calculate the amount of power an app used for this subsystem. * @param app The UidBatteryConsumer.Builder that represents the power use of an app. * @param u The recorded stats for the app. * @param rawRealtimeUs The raw system realtime in microseconds. @@ -145,38 +99,7 @@ public abstract class PowerCalculator { * Prints formatted amount of power in milli-amp-hours. */ public static void printPowerMah(PrintWriter pw, double powerMah) { - pw.print(formatCharge(powerMah)); - } - - /** - * Converts charge in mAh to string. - */ - public static String formatCharge(double power) { - if (power == 0) return "0"; - - final String format; - if (power < .00001) { - format = "%.8f"; - } else if (power < .0001) { - format = "%.7f"; - } else if (power < .001) { - format = "%.6f"; - } else if (power < .01) { - format = "%.5f"; - } else if (power < .1) { - format = "%.4f"; - } else if (power < 1) { - format = "%.3f"; - } else if (power < 10) { - format = "%.2f"; - } else if (power < 100) { - format = "%.1f"; - } else { - format = "%.0f"; - } - - // Use English locale because this is never used in UI (only in checkin and dump). - return String.format(Locale.ENGLISH, format, power); + pw.print(BatteryStats.formatCharge(powerMah)); } static double uCtoMah(long chargeUC) { diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java index 110b6b61c730..67d3d6e7bede 100644 --- a/core/java/com/android/internal/os/ScreenPowerCalculator.java +++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java @@ -24,7 +24,6 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.text.format.DateUtils; import android.util.Slog; import android.util.SparseArray; @@ -32,8 +31,6 @@ import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; -import java.util.List; - /** * Estimates power consumed by the screen(s) */ @@ -125,48 +122,6 @@ public class ScreenPowerCalculator extends PowerCalculator { } /** - * Screen power is the additional power the screen takes while the device is running. - */ - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - final PowerAndDuration totalPowerAndDuration = new PowerAndDuration(); - final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats, - rawRealtimeUs, statsType, consumptionUC); - if (totalPowerAndDuration.powerMah == 0) { - return; - } - - // First deal with the SCREEN BatterySipper (since we need this for smearing over apps). - final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0); - bs.usagePowerMah = totalPowerAndDuration.powerMah; - bs.usageTimeMs = totalPowerAndDuration.durationMs; - bs.sumPower(); - sippers.add(bs); - - // Now deal with each app's BatterySipper. The results are stored in the screenPowerMah - // field, which is considered smeared, but the method depends on the data source. - switch (powerModel) { - case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: - final PowerAndDuration appPowerAndDuration = new PowerAndDuration(); - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj, - rawRealtimeUs); - app.screenPowerMah = appPowerAndDuration.powerMah; - } - } - break; - case BatteryConsumer.POWER_MODEL_POWER_PROFILE: - default: - smearScreenBatterySipper(sippers, bs, rawRealtimeUs); - } - } - - /** * Stores duration and power information in totalPowerAndDuration. */ private void calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration, @@ -219,7 +174,7 @@ public class ScreenPowerCalculator extends PowerCalculator { brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; if (DEBUG && binPowerMah != 0) { Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime - + " power=" + formatCharge(binPowerMah)); + + " power=" + BatteryStats.formatCharge(binPowerMah)); } power += binPowerMah; } @@ -228,37 +183,8 @@ public class ScreenPowerCalculator extends PowerCalculator { } /** - * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity - * time, and store this in the {@link BatterySipper#screenPowerMah} field. - */ - @VisibleForTesting - public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper, - long rawRealtimeUs) { - long totalActivityTimeMs = 0; - final SparseLongArray activityTimeArray = new SparseLongArray(); - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatteryStats.Uid uid = sippers.get(i).uidObj; - if (uid != null) { - final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs); - activityTimeArray.put(uid.getUid(), timeMs); - totalActivityTimeMs += timeMs; - } - } - - if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) { - final double totalScreenPowerMah = screenSipper.totalPowerMah; - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper sipper = sippers.get(i); - sipper.screenPowerMah = totalScreenPowerMah - * activityTimeArray.get(sipper.getUid(), 0) - / totalActivityTimeMs; - } - } - } - - /** - * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity - * time, and store this in the {@link BatterySipper#screenPowerMah} field. + * Smear the screen on power usage among {@code UidBatteryConsumers}, based on ratio of + * foreground activity time. */ private void smearScreenBatteryDrain( SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders, diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java index 495a6d9aae50..4a9c91d14c4c 100644 --- a/core/java/com/android/internal/os/SensorPowerCalculator.java +++ b/core/java/com/android/internal/os/SensorPowerCalculator.java @@ -72,12 +72,6 @@ public class SensorPowerCalculator extends PowerCalculator { return powerMah; } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - app.sensorPowerMah = calculatePowerMah(u, rawRealtimeUs, statsType); - } - private long calculateDuration(BatteryStats.Uid u, long rawRealtimeUs, int statsType) { long durationMs = 0; final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats(); diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java index d7872badd0a2..3a3df87f94af 100644 --- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java +++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java @@ -22,12 +22,9 @@ import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; -import java.util.List; - /** * Estimates the amount of power consumed by the System Server handling requests from * a given app. @@ -121,55 +118,6 @@ public class SystemServicePowerCalculator extends PowerCalculator { systemServicePowerMah); } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, - SparseArray<UserHandle> asUsers) { - final BatteryStats.Uid systemUid = batteryStats.getUidStats().get(Process.SYSTEM_UID); - if (systemUid == null) { - return; - } - - final long consumptionUC = systemUid.getCpuMeasuredBatteryConsumptionUC(); - double systemServicePowerMah; - if (getPowerModel(consumptionUC) == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { - systemServicePowerMah = calculatePowerUsingMeasuredConsumption(batteryStats, - systemUid, consumptionUC); - } else { - systemServicePowerMah = calculatePowerUsingPowerProfile(batteryStats); - } - - BatterySipper systemServerSipper = null; - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - if (app.getUid() == Process.SYSTEM_UID) { - systemServerSipper = app; - break; - } - } - } - - if (systemServerSipper != null) { - systemServicePowerMah = Math.min(systemServicePowerMah, systemServerSipper.sumPower()); - - // The system server power needs to be adjusted because part of it got - // distributed to applications - systemServerSipper.powerReattributedToOtherSippersMah = -systemServicePowerMah; - } - - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - if (app != systemServerSipper) { - final BatteryStats.Uid uid = app.uidObj; - app.systemServiceCpuPowerMah = - systemServicePowerMah * uid.getProportionalSystemServiceUsage(); - } - } - } - } - private double calculatePowerUsingMeasuredConsumption(BatteryStats batteryStats, BatteryStats.Uid systemUid, long consumptionUC) { // Use the PowerProfile based model to estimate the ratio between the power consumed diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java index b590bf77283b..22cff6e2435a 100644 --- a/core/java/com/android/internal/os/UserPowerCalculator.java +++ b/core/java/com/android/internal/os/UserPowerCalculator.java @@ -27,8 +27,6 @@ import android.util.SparseArray; import com.android.internal.util.ArrayUtils; -import java.util.List; - /** * Computes power consumed by Users */ @@ -65,40 +63,4 @@ public class UserPowerCalculator extends PowerCalculator { } } } - - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null); - if (forAllUsers) { - return; - } - - SparseArray<BatterySipper> userSippers = new SparseArray<>(); - - for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper sipper = sippers.get(i); - final int uid = sipper.getUid(); - final int userId = UserHandle.getUserId(uid); - if (asUsers.get(userId) == null - && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) { - // We are told to just report this user's apps as one accumulated entry. - BatterySipper userSipper = userSippers.get(userId); - if (userSipper == null) { - userSipper = new BatterySipper(BatterySipper.DrainType.USER, null, 0); - userSipper.userId = userId; - userSippers.put(userId, userSipper); - } - userSipper.add(sipper); - sipper.isAggregated = true; - } - } - - for (int i = 0; i < userSippers.size(); i++) { - BatterySipper sipper = userSippers.valueAt(i); - if (sipper.sumPower() > 0) { - sippers.add(sipper); - } - } - } } diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java index bceb209f62b1..0251e1ccc885 100644 --- a/core/java/com/android/internal/os/WakelockPowerCalculator.java +++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java @@ -21,13 +21,10 @@ import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; -import java.util.List; - public class WakelockPowerCalculator extends PowerCalculator { private static final String TAG = "WakelockPowerCalculator"; private static final boolean DEBUG = PowerCalculator.DEBUG; @@ -105,42 +102,6 @@ public class WakelockPowerCalculator extends PowerCalculator { appPowerMah); } - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - final PowerAndDuration result = new PowerAndDuration(); - BatterySipper osSipper = null; - double osPowerMah = 0; - long osDurationMs = 0; - long totalAppDurationMs = 0; - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - calculateApp(result, app.uidObj, rawRealtimeUs, statsType); - app.wakeLockTimeMs = result.durationMs; - app.wakeLockPowerMah = result.powerMah; - totalAppDurationMs += result.durationMs; - - if (app.getUid() == Process.ROOT_UID) { - osSipper = app; - osPowerMah = result.powerMah; - osDurationMs = result.durationMs; - } - } - } - - // The device has probably been awake for longer than the screen on - // time and application wake lock time would account for. Assign - // this remainder to the OS, if possible. - if (osSipper != null) { - calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, - osPowerMah, osDurationMs, totalAppDurationMs); - osSipper.wakeLockTimeMs = result.durationMs; - osSipper.wakeLockPowerMah = result.powerMah; - osSipper.sumPower(); - } - } - private void calculateApp(PowerAndDuration result, BatteryStats.Uid u, long rawRealtimeUs, int statsType) { long wakeLockTimeUs = 0; @@ -163,7 +124,7 @@ public class WakelockPowerCalculator extends PowerCalculator { result.powerMah = mPowerEstimator.calculatePower(result.durationMs); if (DEBUG && result.powerMah != 0) { Log.d(TAG, "UID " + u.getUid() + ": wake " + result.durationMs - + " power=" + formatCharge(result.powerMah)); + + " power=" + BatteryStats.formatCharge(result.powerMah)); } } @@ -175,7 +136,8 @@ public class WakelockPowerCalculator extends PowerCalculator { if (wakeTimeMillis > 0) { final double power = mPowerEstimator.calculatePower(wakeTimeMillis); if (DEBUG) { - Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + formatCharge(power)); + Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + + " power " + BatteryStats.formatCharge(power)); } result.durationMs = osDurationMs + wakeTimeMillis; result.powerMah = osPowerMah + power; diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index ad291a4ebb73..8c3fb86331b2 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -19,14 +19,11 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Process; import android.os.UidBatteryConsumer; -import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import java.util.Arrays; -import java.util.List; /** * WiFi power calculator for when BatteryStats supports energy reporting @@ -156,62 +153,6 @@ public class WifiPowerCalculator extends PowerCalculator { totalAppPowerMah, powerModel); } - /** - * We do per-app blaming of WiFi activity. If energy info is reported from the controller, - * then only the WiFi process gets blamed here since we normalize power calculations and - * assign all the power drain to apps. If energy info is not reported, we attribute the - * difference between total running time of WiFi for all apps and the actual running time - * of WiFi to the WiFi subsystem. - */ - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - - final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - - long totalAppDurationMs = 0; - double totalAppPowerMah = 0; - final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); - for (int i = sippers.size() - 1; i >= 0; i--) { - final BatterySipper app = sippers.get(i); - if (app.drainType == BatterySipper.DrainType.APP) { - final long consumptionUC = - app.uidObj.getWifiMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - calculateApp(powerDurationAndTraffic, app.uidObj, powerModel, rawRealtimeUs, - statsType, batteryStats.hasWifiActivityReporting(), consumptionUC); - - totalAppDurationMs += powerDurationAndTraffic.durationMs; - totalAppPowerMah += powerDurationAndTraffic.powerMah; - - app.wifiPowerMah = powerDurationAndTraffic.powerMah; - app.wifiRunningTimeMs = powerDurationAndTraffic.durationMs; - app.wifiRxBytes = powerDurationAndTraffic.wifiRxBytes; - app.wifiRxPackets = powerDurationAndTraffic.wifiRxPackets; - app.wifiTxBytes = powerDurationAndTraffic.wifiTxBytes; - app.wifiTxPackets = powerDurationAndTraffic.wifiTxPackets; - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); - } - } - } - - final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC(); - final int powerModel = getPowerModel(consumptionUC); - calculateRemaining(powerDurationAndTraffic, powerModel, batteryStats, rawRealtimeUs, - statsType, batteryStats.hasWifiActivityReporting(), totalAppDurationMs, - totalAppPowerMah, consumptionUC); - - bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs; - bs.wifiPowerMah += powerDurationAndTraffic.powerMah; - - if (bs.sumPower() > 0) { - sippers.add(bs); - } - } - private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic, BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel, long rawRealtimeUs, int statsType, boolean hasWifiActivityReporting, @@ -251,7 +192,7 @@ public class WifiPowerCalculator extends PowerCalculator { if (DEBUG && powerDurationAndTraffic.powerMah != 0) { Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime - + "ms tx=" + txTime + "ms power=" + formatCharge( + + "ms tx=" + txTime + "ms power=" + BatteryStats.formatCharge( powerDurationAndTraffic.powerMah)); } @@ -306,7 +247,7 @@ public class WifiPowerCalculator extends PowerCalculator { } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge( + Log.d(TAG, "UID " + u.getUid() + ": power=" + BatteryStats.formatCharge( powerDurationAndTraffic.powerMah)); } } @@ -353,7 +294,8 @@ public class WifiPowerCalculator extends PowerCalculator { powerDurationAndTraffic.powerMah = Math.max(0, totalPowerMah - totalAppPowerMah); if (DEBUG) { - Log.d(TAG, "left over WiFi power: " + formatCharge(powerDurationAndTraffic.powerMah)); + Log.d(TAG, "left over WiFi power: " + BatteryStats.formatCharge( + powerDurationAndTraffic.powerMah)); } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index f258f84c5e3e..3d24aa2db247 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -736,10 +736,13 @@ public class ZygoteInit { Zygote.applyInvokeWithSystemProperty(parsedArgs); if (Zygote.nativeSupportsMemoryTagging()) { - /* The system server has ASYNC MTE by default, in order to allow - * system services to specify their own MTE level later, as you - * can't re-enable MTE once it's disabled. */ - String mode = SystemProperties.get("arm64.memtag.process.system_server", "async"); + String mode = SystemProperties.get("arm64.memtag.process.system_server", ""); + if (mode.isEmpty()) { + /* The system server has ASYNC MTE by default, in order to allow + * system services to specify their own MTE level later, as you + * can't re-enable MTE once it's disabled. */ + mode = SystemProperties.get("persist.arm64.memtag.default", "async"); + } if (mode.equals("async")) { parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_ASYNC; } else if (mode.equals("sync")) { diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index e72afdd78ba4..8430c0859bc9 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -24,7 +24,9 @@ import com.android.internal.inputmethod.InputBindResult; */ oneway interface IInputMethodClient { void onBindMethod(in InputBindResult res); + void onBindAccessibilityService(in InputBindResult res, int id); void onUnbindMethod(int sequence, int unbindReason); + void onUnbindAccessibilityService(int sequence, int id); void setActive(boolean active, boolean fullscreen, boolean reportToImeController); void scheduleStartInputIfNecessary(boolean fullscreen); void reportFullscreenMode(boolean fullscreen); diff --git a/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl b/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl new file mode 100644 index 000000000000..8fbdefe5931e --- /dev/null +++ b/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2013 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.internal.view; + + import com.android.internal.view.IInputMethodSession; + +/** + * Helper interface for IInputMethod to allow the input method to notify the client when a new + * session has been created. + */ +oneway interface IInputSessionWithIdCallback { + void sessionCreated(IInputMethodSession session, int id); +}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index c9dc6b617f8c..1e11c6d1fddb 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -26,6 +26,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; import android.app.admin.PasswordMetrics; import android.app.trust.IStrongAuthTracker; @@ -941,17 +942,53 @@ public class LockPatternUtils { } /** + * Retrieve the credential type of a user. + */ + private final PropertyInvalidatedCache.QueryHandler<Integer, Integer> mCredentialTypeQuery = + new PropertyInvalidatedCache.QueryHandler<>() { + @Override + public Integer apply(Integer userHandle) { + try { + return getLockSettings().getCredentialType(userHandle); + } catch (RemoteException re) { + Log.e(TAG, "failed to get credential type", re); + return CREDENTIAL_TYPE_NONE; + } + } + @Override + public boolean shouldBypassCache(Integer userHandle) { + return userHandle == USER_FRP; + } + }; + + /** + * The API that is cached. + */ + private final static String CREDENTIAL_TYPE_API = "getCredentialType"; + + /** + * Cache the credential type of a user. + */ + private final PropertyInvalidatedCache<Integer, Integer> mCredentialTypeCache = + new PropertyInvalidatedCache<>(4, PropertyInvalidatedCache.MODULE_SYSTEM, + CREDENTIAL_TYPE_API, CREDENTIAL_TYPE_API, mCredentialTypeQuery); + + /** + * Invalidate the credential cache + * @hide + */ + public final static void invalidateCredentialTypeCache() { + PropertyInvalidatedCache.invalidateCache(PropertyInvalidatedCache.MODULE_SYSTEM, + CREDENTIAL_TYPE_API); + } + + /** * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE}, * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and * {@link #CREDENTIAL_TYPE_PASSWORD} */ public @CredentialType int getCredentialTypeForUser(int userHandle) { - try { - return getLockSettings().getCredentialType(userHandle); - } catch (RemoteException re) { - Log.e(TAG, "failed to get credential type", re); - return CREDENTIAL_TYPE_NONE; - } + return mCredentialTypeCache.query(userHandle); } /** diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 55f136997aad..4f13a9cf257e 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -30,6 +30,11 @@ namespace android { +static struct { + jclass clazz; + jmethodID ctor; +} gTransactionClassInfo; + static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) { ScopedUtfChars name(env, jName); sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str()); @@ -86,6 +91,14 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl)); } +static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr, + jlong frameNum) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + SurfaceComposerClient::Transaction* transaction = queue->gatherPendingTransactions(frameNum); + return env->NewObject(gTransactionClassInfo.clazz, gTransactionClassInfo.ctor, + reinterpret_cast<jlong>(transaction)); +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off @@ -98,6 +111,7 @@ static const JNINativeMethod gMethods[] = { {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions}, {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl}, + {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions} // clang-format on }; @@ -105,6 +119,11 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/graphics/BLASTBufferQueue", gMethods, NELEM(gMethods)); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + + jclass transactionClazz = FindClassOrDie(env, "android/view/SurfaceControl$Transaction"); + gTransactionClassInfo.clazz = MakeGlobalRefOrDie(env, transactionClazz); + gTransactionClassInfo.ctor = + GetMethodIDOrDie(env, gTransactionClassInfo.clazz, "<init>", "(J)V"); return 0; } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 2bec733d954c..dc55c0512941 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2476,37 +2476,47 @@ static jint android_media_AudioSystem_getMinSampleRate(JNIEnv *env, jobject thiz return 4000; // SAMPLE_RATE_HZ_MIN (for API) } -static jint -android_media_AudioSystem_setAssistantUid(JNIEnv *env, jobject thiz, jint uid) -{ - status_t status = AudioSystem::setAssistantUid(uid); +static std::vector<uid_t> convertJIntArrayToUidVector(JNIEnv *env, jintArray jArray) { + std::vector<uid_t> nativeVector; + if (jArray != nullptr) { + jsize len = env->GetArrayLength(jArray); + + if (len > 0) { + int *nativeArray = nullptr; + nativeArray = env->GetIntArrayElements(jArray, 0); + if (nativeArray != nullptr) { + for (size_t i = 0; i < len; i++) { + nativeVector.push_back(nativeArray[i]); + } + env->ReleaseIntArrayElements(jArray, nativeArray, 0); + } + } + } + return nativeVector; +} + +static jint android_media_AudioSystem_setAssistantServicesUids(JNIEnv *env, jobject thiz, + jintArray uids) { + std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids); + + status_t status = AudioSystem::setAssistantServicesUids(nativeUidsVector); + return (jint)nativeToJavaStatus(status); } -static jint android_media_AudioSystem_setHotwordDetectionServiceUid(JNIEnv *env, jobject thiz, - jint uid) { - status_t status = AudioSystem::setHotwordDetectionServiceUid(uid); +static jint android_media_AudioSystem_setActiveAssistantServicesUids(JNIEnv *env, jobject thiz, + jintArray activeUids) { + std::vector<uid_t> nativeActiveUidsVector = convertJIntArrayToUidVector(env, activeUids); + + status_t status = AudioSystem::setActiveAssistantServicesUids(nativeActiveUidsVector); + return (jint)nativeToJavaStatus(status); } static jint android_media_AudioSystem_setA11yServicesUids(JNIEnv *env, jobject thiz, jintArray uids) { - std::vector<uid_t> nativeUidsVector; - - if (uids != nullptr) { - jsize len = env->GetArrayLength(uids); - - if (len > 0) { - int *nativeUids = nullptr; - nativeUids = env->GetIntArrayElements(uids, 0); - if (nativeUids != nullptr) { - for (size_t i = 0; i < len; i++) { - nativeUidsVector.push_back(nativeUids[i]); - } - env->ReleaseIntArrayElements(uids, nativeUids, 0); - } - } - } + std::vector<uid_t> nativeUidsVector = convertJIntArrayToUidVector(env, uids); + status_t status = AudioSystem::setA11yServicesUids(nativeUidsVector); return (jint)nativeToJavaStatus(status); } @@ -3000,9 +3010,10 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_getReportedSurroundFormats}, {"setSurroundFormatEnabled", "(IZ)I", (void *)android_media_AudioSystem_setSurroundFormatEnabled}, - {"setAssistantUid", "(I)I", (void *)android_media_AudioSystem_setAssistantUid}, - {"setHotwordDetectionServiceUid", "(I)I", - (void *)android_media_AudioSystem_setHotwordDetectionServiceUid}, + {"setAssistantServicesUids", "([I)I", + (void *)android_media_AudioSystem_setAssistantServicesUids}, + {"setActiveAssistantServicesUids", "([I)I", + (void *)android_media_AudioSystem_setActiveAssistantServicesUids}, {"setA11yServicesUids", "([I)I", (void *)android_media_AudioSystem_setA11yServicesUids}, {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported}, diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp index 3af55fe810fc..d2d7213e5761 100644 --- a/core/jni/android_os_HwRemoteBinder.cpp +++ b/core/jni/android_os_HwRemoteBinder.cpp @@ -81,27 +81,37 @@ public: void binderDied(const wp<hardware::IBinder>& who) { - if (mObject != NULL) { - JNIEnv* env = javavm_to_jnienv(mVM); + JNIEnv* env = javavm_to_jnienv(mVM); + + // Serialize with our containing HwBinderDeathRecipientList so that we can't + // delete the global ref on object while the list is being iterated. + sp<HwBinderDeathRecipientList> list = mList.promote(); + if (list == nullptr) return; - env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice, mObject, mCookie); + jobject object; + { + AutoMutex _l(list->lock()); + + // this function now owns the global ref - to the rest of the code, it looks like + // this binder already died, but we won't actually delete the reference until + // the Java code has processed the death + object = mObject; + + // Demote from strong ref to weak for after binderDied() has been delivered, + // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed. + mObjectWeak = env->NewWeakGlobalRef(mObject); + mObject = nullptr; + } + + if (object != nullptr) { + env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice, + object, mCookie); if (env->ExceptionCheck()) { ALOGE("Uncaught exception returned from death notification."); env->ExceptionClear(); } - // Serialize with our containing HwBinderDeathRecipientList so that we can't - // delete the global ref on mObject while the list is being iterated. - sp<HwBinderDeathRecipientList> list = mList.promote(); - if (list != NULL) { - AutoMutex _l(list->lock()); - - // Demote from strong ref to weak after binderDied() has been delivered, - // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed. - mObjectWeak = env->NewWeakGlobalRef(mObject); - env->DeleteGlobalRef(mObject); - mObject = NULL; - } + env->DeleteGlobalRef(object); } } @@ -115,7 +125,7 @@ public: } } - bool matches(jobject obj) { + bool matchesLocked(jobject obj) { bool result; JNIEnv* env = javavm_to_jnienv(mVM); @@ -129,7 +139,7 @@ public: return result; } - void warnIfStillLive() { + void warnIfStillLiveLocked() { if (mObject != NULL) { // Okay, something is wrong -- we have a hard reference to a live death // recipient on the VM side, but the list is being torn down. @@ -176,7 +186,7 @@ HwBinderDeathRecipientList::~HwBinderDeathRecipientList() { AutoMutex _l(mLock); for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) { - deathRecipient->warnIfStillLive(); + deathRecipient->warnIfStillLiveLocked(); } } @@ -201,7 +211,7 @@ sp<HwBinderDeathRecipient> HwBinderDeathRecipientList::find(jobject recipient) { AutoMutex _l(mLock); for(auto iter = mList.rbegin(); iter != mList.rend(); iter++) { - if ((*iter)->matches(recipient)) { + if ((*iter)->matchesLocked(recipient)) { return (*iter); } } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 2488b57c7f2a..336161c8c37e 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -245,6 +245,13 @@ static struct { jmethodID onTransactionCommitted; } gTransactionCommittedListenerClassInfo; +static struct { + jclass clazz; + jmethodID ctor; + jfieldID format; + jfieldID alphaInterpretation; +} gDisplayDecorationSupportInfo; + class JNamedColorSpace { public: // ColorSpace.Named.SRGB.ordinal() = 0; @@ -1792,13 +1799,29 @@ static void nativeSetGlobalShadowSettings(JNIEnv* env, jclass clazz, jfloatArray client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius); } -static jboolean nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz, - jobject displayTokenObject) { +static jobject nativeGetDisplayDecorationSupport(JNIEnv* env, jclass clazz, + jobject displayTokenObject) { sp<IBinder> displayToken(ibinderForJavaObject(env, displayTokenObject)); if (displayToken == nullptr) { - return JNI_FALSE; + return nullptr; + } + const auto support = SurfaceComposerClient::getDisplayDecorationSupport(displayToken); + if (!support) { + return nullptr; + } + + jobject jDisplayDecorationSupport = + env->NewObject(gDisplayDecorationSupportInfo.clazz, gDisplayDecorationSupportInfo.ctor); + if (jDisplayDecorationSupport == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return nullptr; } - return static_cast<jboolean>(SurfaceComposerClient::getDisplayDecorationSupport(displayToken)); + + env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.format, + static_cast<jint>(support.value().format)); + env->SetIntField(jDisplayDecorationSupport, gDisplayDecorationSupportInfo.alphaInterpretation, + static_cast<jint>(support.value().alphaInterpretation)); + return jDisplayDecorationSupport; } static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { @@ -2131,7 +2154,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeMirrorSurface }, {"nativeSetGlobalShadowSettings", "([F[FFFF)V", (void*)nativeSetGlobalShadowSettings }, - {"nativeGetDisplayDecorationSupport", "(Landroid/os/IBinder;)Z", + {"nativeGetDisplayDecorationSupport", + "(Landroid/os/IBinder;)Landroid/hardware/graphics/common/DisplayDecorationSupport;", (void*)nativeGetDisplayDecorationSupport}, {"nativeGetHandle", "(J)J", (void*)nativeGetHandle }, @@ -2390,6 +2414,17 @@ int register_android_view_SurfaceControl(JNIEnv* env) gTransactionCommittedListenerClassInfo.onTransactionCommitted = GetMethodIDOrDie(env, transactionCommittedListenerClazz, "onTransactionCommitted", "()V"); + + jclass displayDecorationSupportClazz = + FindClassOrDie(env, "android/hardware/graphics/common/DisplayDecorationSupport"); + gDisplayDecorationSupportInfo.clazz = MakeGlobalRefOrDie(env, displayDecorationSupportClazz); + gDisplayDecorationSupportInfo.ctor = + GetMethodIDOrDie(env, displayDecorationSupportClazz, "<init>", "()V"); + gDisplayDecorationSupportInfo.format = + GetFieldIDOrDie(env, displayDecorationSupportClazz, "format", "I"); + gDisplayDecorationSupportInfo.alphaInterpretation = + GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I"); + return err; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2736ba630902..19c132549e8f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -199,6 +199,8 @@ <protected-broadcast android:name="android.bluetooth.headsetclient.profile.action.LAST_VTAG" /> <protected-broadcast + android:name="android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" /> @@ -721,6 +723,7 @@ <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" /> <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" /> <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" /> + <protected-broadcast android:name="android.app.action.PROVISIONING_COMPLETED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -1895,7 +1898,7 @@ android:label="@string/permlab_changeWifiState" android:protectionLevel="normal" /> - <!-- @SystemApi @hide Allows applications to enable/disable wifi auto join. This permission + <!-- Allows applications to enable/disable wifi auto join. This permission is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs to improve wifi performance. <p>Not for use by third-party applications. --> @@ -1933,7 +1936,7 @@ <permission android:name="android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE" android:protectionLevel="signature|privileged" /> - <!-- @SystemApi @hide Allows an application to modify any wifi configuration, even if created + <!-- Allows an application to modify any wifi configuration, even if created by another application. Once reconfigured the original creator cannot make any further modifications. <p>Not for use by third-party applications. --> @@ -4400,6 +4403,16 @@ <permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:protectionLevel="normal|appop"/> + <!-- Allows apps to use exact alarms just like with SCHEDULE_EXACT_ALARM but without needing + to request this permission from the user. + <p><b>This is only for apps that rely on exact alarms for their core functionality.</b> + App stores may enforce policies to audit and review the use of this permission. Any app that + requests this but is found to not require exact alarms for its primary function may be + removed from the app store. + --> + <permission android:name="android.permission.USE_EXACT_ALARM" + android:protectionLevel="normal"/> + <!-- Allows an application to query tablet mode state and monitor changes in it. <p>Not for use by third-party applications. @@ -6616,14 +6629,6 @@ android:exported="false"> </activity> - <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity" - android:theme="@style/Theme.Dialog.Confirmation" - android:excludeFromRecents="true" - android:process=":ui" - android:label="@string/log_access_confirmation_title" - android:exported="false"> - </activity> - <activity android:name="com.android.server.notification.NASLearnMoreActivity" android:theme="@style/Theme.Dialog.Confirmation" android:excludeFromRecents="true" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 8df587157791..3a9cb2e52ce7 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2332,6 +2332,17 @@ contrast between the window background and the icon. Note the shape would also be masking like an icon. --> <attr name="windowSplashScreenIconBackgroundColor" format="color"/> + + <!-- Specify whether this application always wants the icon to be displayed on the splash + screen. --> + <attr name="windowSplashScreenBehavior"> + <!-- The icon is shown when the launching activity sets the splashScreenStyle to + SPLASH_SCREEN_STYLE_ICON. If the launching activity does not specify any style, + follow the system behavior. --> + <enum name="default" value="0" /> + <!-- The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY --> + <enum name="icon_preferred" value="1" /> + </attr> </declare-styleable> <!-- The set of attributes that describe a AlertDialog's theme. --> @@ -3910,6 +3921,8 @@ <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_MULTI_FINGER_GESTURES}. --> <flag name="flagRequestMultiFingerGestures" value="0x00001000" /> <flag name="flagSendMotionEvents" value="0x0004000" /> + <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR}. --> + <flag name="flagInputMethodEditor" value="0x0008000" /> </attr> <!-- Component name of an activity that allows the user to modify the settings for this service. This setting cannot be changed at runtime. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6a86b1cc42dd..775527df86d8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4082,6 +4082,16 @@ --> <string name="config_defaultAmbientContextDetectionService" translatable="false"></string> + <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent. --> + <string translatable="false" name="config_defaultAmbientContextConsentComponent"></string> + + <!-- Intent extra key for the caller's package name while requesting ambient context consent. + --> + <string translatable="false" name="config_ambientContextPackageNameExtraKey"></string> + + <!-- Intent extra key for the event code int array while requesting ambient context consent. --> + <string translatable="false" name="config_ambientContextEventArrayExtraKey"></string> + <!-- The component name for the system-wide captions service. This service must be trusted, as it controls part of the UI of the volume bar. Example: "com.android.captions/.SystemCaptionsService" diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index f05137e144d9..cc63fd6146a7 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3275,6 +3275,7 @@ <public name="toExtendRight" /> <public name="toExtendBottom" /> <public name="tileService" /> + <public name="windowSplashScreenBehavior" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 70ca8fc638eb..6297ed90fcba 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5719,20 +5719,6 @@ <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] --> <string name="harmful_app_warning_title">Harmful app detected</string> - <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] --> - <string name="log_access_confirmation_title">System log access request</string> - <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] --> - <string name="log_access_confirmation_allow">Only this time</string> - <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] --> - <string name="log_access_confirmation_deny">Don\u2019t allow</string> - - <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> - <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging. - These logs might contain information that apps and services on your device have written.</string> - - <!-- Privacy notice do not show [CHAR LIMIT=20] --> - <string name="log_access_do_not_show_again">Don\u2019t show again</string> - <!-- Text describing a permission request for one app to show another app's slices [CHAR LIMIT=NONE] --> <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a79eec28e248..d731180bd56a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3868,11 +3868,6 @@ <java-symbol type="string" name="harmful_app_warning_title" /> <java-symbol type="layout" name="harmful_app_warning_dialog" /> - <java-symbol type="string" name="log_access_confirmation_allow" /> - <java-symbol type="string" name="log_access_confirmation_deny" /> - <java-symbol type="string" name="log_access_confirmation_title" /> - <java-symbol type="string" name="log_access_confirmation_body" /> - <java-symbol type="string" name="config_defaultAssistantAccessComponent" /> <java-symbol type="string" name="slices_permission_request" /> diff --git a/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java new file mode 100644 index 000000000000..c1b6666a2d17 --- /dev/null +++ b/core/tests/coretests/src/android/app/activity/RegisterComponentCallbacksTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 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 android.app.activity; + +import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; +import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; + +import android.app.Activity; +import android.app.WindowConfiguration; +import android.app.activity.ActivityThreadTest.TestActivity; +import android.compat.testing.PlatformCompatChangeRule; +import android.content.ComponentCallbacks; +import android.content.TestComponentCallbacks2; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * Test for verifying {@link Activity#registerComponentCallbacks(ComponentCallbacks)} behavior. + * Build/Install/Run: + * atest FrameworksCoreTests:android.app.activity.RegisterComponentCallbacksTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class RegisterComponentCallbacksTest { + @Rule + public ActivityScenarioRule rule = new ActivityScenarioRule<>(TestActivity.class); + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + public void testRegisterComponentCallbacks() { + final ActivityScenario scenario = rule.getScenario(); + final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2(); + final Configuration config = new Configuration(); + config.fontScale = 1.2f; + config.windowConfiguration.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_FREEFORM); + config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100)); + final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW; + + scenario.onActivity(activity -> { + // It should be no-op to unregister a ComponentCallbacks without registration. + activity.unregisterComponentCallbacks(callbacks); + + activity.registerComponentCallbacks(callbacks); + // Verify #onConfigurationChanged + activity.onConfigurationChanged(config); + assertThat(callbacks.mConfiguration).isEqualTo(config); + // Verify #onTrimMemory + activity.onTrimMemory(trimMemoryLevel); + assertThat(callbacks.mLevel).isEqualTo(trimMemoryLevel); + // verify #onLowMemory + activity.onLowMemory(); + assertThat(callbacks.mLowMemoryCalled).isTrue(); + + activity.unregisterComponentCallbacks(callbacks); + }); + } + + @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS) + @Test + public void testRegisterComponentCallbacksBeforeT() { + final ActivityScenario scenario = rule.getScenario(); + final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2(); + final Configuration config = new Configuration(); + config.fontScale = 1.2f; + config.windowConfiguration.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_FREEFORM); + config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100)); + final int trimMemoryLevel = TRIM_MEMORY_RUNNING_LOW; + + scenario.onActivity(activity -> { + // It should be no-op to unregister a ComponentCallbacks without registration. + activity.unregisterComponentCallbacks(callbacks); + + activity.registerComponentCallbacks(callbacks); + // Verify #onConfigurationChanged + activity.onConfigurationChanged(config); + assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext " + + "before T.").that(callbacks.mConfiguration).isNull(); + // Verify #onTrimMemory + activity.onTrimMemory(trimMemoryLevel); + assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext " + + "before T.").that(callbacks.mLevel).isEqualTo(0); + // verify #onLowMemory + activity.onLowMemory(); + assertWithMessage("The ComponentCallbacks must be added to #getApplicationContext " + + "before T.").that(callbacks.mLowMemoryCalled).isFalse(); + + activity.unregisterComponentCallbacks(callbacks); + }); + } +} diff --git a/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java new file mode 100644 index 000000000000..d66cb712b1ca --- /dev/null +++ b/core/tests/coretests/src/android/companion/virtual/audio/VirtualAudioSessionTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 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 android.companion.virtual.audio; + +import static android.media.AudioFormat.CHANNEL_IN_MONO; +import static android.media.AudioFormat.CHANNEL_OUT_MONO; +import static android.media.AudioFormat.ENCODING_PCM_16BIT; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertThrows; + +import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; +import android.content.Context; +import android.content.ContextWrapper; +import android.media.AudioFormat; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class VirtualAudioSessionTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private AudioConfigurationChangeCallback mCallback; + private static final int APP_UID = 100; + private static final int APP_UID2 = 200; + private static final AudioFormat AUDIO_CAPTURE_FORMAT = + new AudioFormat.Builder() + .setSampleRate(48000) + .setEncoding(ENCODING_PCM_16BIT) + .setChannelMask(CHANNEL_IN_MONO) + .build(); + private static final AudioFormat AUDIO_INJECT_FORMAT = + new AudioFormat.Builder() + .setSampleRate(48000) + .setEncoding(ENCODING_PCM_16BIT) + .setChannelMask(CHANNEL_OUT_MONO) + .build(); + private Context mContext; + private VirtualAudioSession mVirtualAudioSession; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mVirtualAudioSession = new VirtualAudioSession( + mContext, mCallback, /* executor= */ null); + } + + @Test + public void startAudioCapture_isSuccessful() { + AudioCapture audioCapture = mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + + assertThat(audioCapture).isNotNull(); + assertThat(mVirtualAudioSession.getAudioCapture()).isEqualTo(audioCapture); + } + + @Test + public void startAudioCapture_audioCaptureAlreadyStarted_throws() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + + assertThrows(IllegalStateException.class, + () -> mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT)); + } + + @Test + public void startAudioInjection_isSuccessful() { + AudioInjection audioInjection = mVirtualAudioSession.startAudioInjection( + AUDIO_INJECT_FORMAT); + + assertThat(audioInjection).isNotNull(); + assertThat(mVirtualAudioSession.getAudioInjection()).isEqualTo(audioInjection); + } + + @Test + public void startAudioInjection_audioInjectionAlreadyStarted_throws() { + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + + assertThrows(IllegalStateException.class, + () -> mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT)); + } + + @Test + public void onAppsNeedingAudioRoutingChanged_neverStartAudioCaptureOrInjection_throws() { + int[] uids = new int[]{APP_UID}; + + assertThrows(IllegalStateException.class, + () -> mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(uids)); + } + + @Test + public void onAppsNeedingAudioRoutingChanged_cachesReroutedApps() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + int[] appUids = new int[]{APP_UID}; + + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(appUids); + + assertThat(Arrays.equals(mVirtualAudioSession.getReroutedAppUids().toArray(), + appUids)).isTrue(); + } + + @Test + public void onAppsNeedingAudioRoutingChanged_receiveManyTimes_reroutedAppsSizeIsCorrect() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + int[] appUids = new int[]{APP_UID, APP_UID2}; + + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(new int[]{1234}); + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(new int[]{5678}); + mVirtualAudioSession.onAppsNeedingAudioRoutingChanged(appUids); + + assertThat(Arrays.equals(mVirtualAudioSession.getReroutedAppUids().toArray(), + appUids)).isTrue(); + assertThat(mVirtualAudioSession.getReroutedAppUids().size()).isEqualTo(2); + } + + @Test + public void close_releasesCaptureAndInjection() { + mVirtualAudioSession.startAudioCapture(AUDIO_CAPTURE_FORMAT); + mVirtualAudioSession.startAudioInjection(AUDIO_INJECT_FORMAT); + + mVirtualAudioSession.close(); + + assertThat(mVirtualAudioSession.getAudioCapture()).isNull(); + assertThat(mVirtualAudioSession.getAudioInjection()).isNull(); + } + + @Test + public void onPlaybackConfigChanged_sendsCallback() { + List<AudioPlaybackConfiguration> configs = new ArrayList<>(); + + mVirtualAudioSession.onPlaybackConfigChanged(configs); + + verify(mCallback).onPlaybackConfigChanged(configs); + } + + @Test + public void onRecordingConfigChanged_sendCallback() { + List<AudioRecordingConfiguration> configs = new ArrayList<>(); + + mVirtualAudioSession.onRecordingConfigChanged(configs); + + verify(mCallback).onRecordingConfigChanged(configs); + } +} diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java index ecaf1f42fbca..495770245b49 100644 --- a/core/tests/coretests/src/android/content/ContextWrapperTest.java +++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java @@ -16,7 +16,7 @@ package android.content; -import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER; +import static android.content.Context.OVERRIDABLE_COMPONENT_CALLBACKS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -61,7 +61,7 @@ public class ContextWrapperTest { * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before * {@link ContextWrapper#attachBaseContext(Context)}. */ - @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER) + @DisableCompatChanges(OVERRIDABLE_COMPONENT_CALLBACKS) @Test public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() { final ContextWrapper wrapper = new TestContextWrapper(null /* base */); diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS index 0b945895ad7f..a69c6ffef8d8 100644 --- a/core/tests/coretests/src/android/content/OWNERS +++ b/core/tests/coretests/src/android/content/OWNERS @@ -1,6 +1,7 @@ per-file AssetTest.java = file:/core/java/android/content/res/OWNERS -per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS +per-file Context* = file:/services/core/java/com/android/server/wm/OWNERS +per-file Context* = charlesccchen@google.com per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS -per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS -per-file ComponentCallbacksControllerTest = charlesccchen@google.com +per-file *ComponentCallbacks* = file:/services/core/java/com/android/server/wm/OWNERS +per-file *ComponentCallbacks* = charlesccchen@google.com diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java index 6ae7fc44c4ce..5c8787aeda9c 100644 --- a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java +++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java @@ -20,10 +20,10 @@ import android.content.res.Configuration; import androidx.annotation.NonNull; -class TestComponentCallbacks2 implements ComponentCallbacks2 { - android.content.res.Configuration mConfiguration; - boolean mLowMemoryCalled; - int mLevel; +public class TestComponentCallbacks2 implements ComponentCallbacks2 { + public Configuration mConfiguration = null; + public boolean mLowMemoryCalled = false; + public int mLevel = 0; @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java deleted file mode 100644 index 260b65a8b463..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - */ - -package com.android.internal.os; - -import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.BatteryStats; -import android.os.Process; -import android.text.format.DateUtils; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import junit.framework.TestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class BatteryStatsHelperTest extends TestCase { - private static final long TIME_FOREGROUND_ACTIVITY_ZERO = 0; - private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS * 1000; - private static final long TIME_STATE_FOREGROUND_MS = 10 * DateUtils.MINUTE_IN_MILLIS; - private static final long TIME_STATE_FOREGROUND_US = TIME_STATE_FOREGROUND_MS * 1000; - - private static final int UID = 123456; - private static final double BATTERY_SCREEN_USAGE = 300; - private static final double BATTERY_SYSTEM_USAGE = 600; - private static final double BATTERY_WIFI_USAGE = 200; - private static final double BATTERY_IDLE_USAGE = 600; - private static final double BATTERY_BLUETOOTH_USAGE = 300; - private static final double BATTERY_OVERACCOUNTED_USAGE = 500; - private static final double BATTERY_UNACCOUNTED_USAGE = 700; - private static final double BATTERY_APP_USAGE = 100; - private static final double TOTAL_BATTERY_USAGE = 1000; - private static final double PRECISION = 0.001; - - @Mock - private BatteryStats.Uid mUid; - @Mock - private BatterySipper mWifiBatterySipper; - @Mock - private BatterySipper mBluetoothBatterySipper; - @Mock - private BatterySipper mIdleBatterySipper; - @Mock - private BatterySipper mNormalBatterySipper; - @Mock - private BatterySipper mScreenBatterySipper; - @Mock - private BatterySipper mOvercountedBatterySipper; - @Mock - private BatterySipper mUnaccountedBatterySipper; - @Mock - private BatterySipper mSystemBatterySipper; - @Mock - private BatterySipper mCellBatterySipper; - @Mock - private PackageManager mPackageManager; - - private BatteryStatsHelper mBatteryStatsHelper; - private Context mContext; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; - mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE; - when(mNormalBatterySipper.getUid()).thenReturn(UID); - mNormalBatterySipper.uidObj = mUid; - - - mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN; - mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE; - - mSystemBatterySipper.drainType = BatterySipper.DrainType.APP; - mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE; - mSystemBatterySipper.uidObj = mUid; - when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID); - - mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; - mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERACCOUNTED_USAGE; - - mUnaccountedBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; - mUnaccountedBatterySipper.totalPowerMah = BATTERY_UNACCOUNTED_USAGE; - - mWifiBatterySipper.drainType = BatterySipper.DrainType.WIFI; - mWifiBatterySipper.totalPowerMah = BATTERY_WIFI_USAGE; - - mBluetoothBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH; - mBluetoothBatterySipper.totalPowerMah = BATTERY_BLUETOOTH_USAGE; - - mIdleBatterySipper.drainType = BatterySipper.DrainType.IDLE; - mIdleBatterySipper.totalPowerMah = BATTERY_IDLE_USAGE; - - mContext = InstrumentationRegistry.getContext(); - mBatteryStatsHelper = spy(new BatteryStatsHelper(mContext)); - mBatteryStatsHelper.setPackageManager(mPackageManager); - } - - @Test - public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED; - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testShouldHideSipper_TypeOverAccounted_ReturnTrue() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED; - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testShouldHideSipper_TypeIdle_ReturnTrue() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE; - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testShouldHideSipper_TypeCell_ReturnTrue() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL; - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testShouldHideSipper_TypeScreen_ReturnTrue() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN; - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testShouldHideSipper_TypeSystem_ReturnTrue() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; - when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID); - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testShouldHideSipper_UidNormal_ReturnFalse() { - mNormalBatterySipper.drainType = BatterySipper.DrainType.APP; - assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isFalse(); - } - - @Test - public void testRemoveHiddenBatterySippers_ContainsHiddenSippers_RemoveAndReturnValue() { - final List<BatterySipper> sippers = new ArrayList<>(); - sippers.add(mNormalBatterySipper); - sippers.add(mScreenBatterySipper); - sippers.add(mSystemBatterySipper); - sippers.add(mOvercountedBatterySipper); - sippers.add(mUnaccountedBatterySipper); - sippers.add(mWifiBatterySipper); - sippers.add(mBluetoothBatterySipper); - sippers.add(mIdleBatterySipper); - doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper); - - final double totalUsage = mBatteryStatsHelper.removeHiddenBatterySippers(sippers); - - assertThat(mNormalBatterySipper.shouldHide).isFalse(); - assertThat(mScreenBatterySipper.shouldHide).isTrue(); - assertThat(mSystemBatterySipper.shouldHide).isTrue(); - assertThat(mOvercountedBatterySipper.shouldHide).isTrue(); - assertThat(mUnaccountedBatterySipper.shouldHide).isTrue(); - assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SYSTEM_USAGE); - } - - @Test - public void testSmearScreenBatterySipper() { - final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class); - final BatterySipper sipperNull = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO, - BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */, spc); - final BatterySipper sipperBg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO, - BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */, spc); - final BatterySipper sipperFg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY, - BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */, spc); - - final List<BatterySipper> sippers = new ArrayList<>(); - sippers.add(sipperNull); - sippers.add(sipperBg); - sippers.add(sipperFg); - - spc.smearScreenBatterySipper(sippers, mScreenBatterySipper, 0); - - assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0); - assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0); - assertThat(sipperFg.screenPowerMah).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE); - } - - @Test - public void testIsTypeSystem_systemPackage_returnTrue() { - final String[] systemPackages = {"com.android.system"}; - mBatteryStatsHelper.setSystemPackageArray(systemPackages); - doReturn(UID).when(mNormalBatterySipper).getUid(); - doReturn(systemPackages).when(mPackageManager).getPackagesForUid(UID); - - assertThat(mBatteryStatsHelper.isTypeSystem(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testIsTypeService_servicePackage_returnTrue() { - final String[] servicePackages = {"com.android.service"}; - mBatteryStatsHelper.setServicePackageArray(servicePackages); - doReturn(UID).when(mNormalBatterySipper).getUid(); - doReturn(servicePackages).when(mPackageManager).getPackagesForUid(UID); - - assertThat(mBatteryStatsHelper.isTypeService(mNormalBatterySipper)).isTrue(); - } - - @Test - public void testGetProcessForegroundTimeMs_largerActivityTime_returnMinTime() { - final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class); - doReturn(TIME_STATE_FOREGROUND_US + 500).when(spc) - .getForegroundActivityTotalTimeUs(eq(mUid), anyLong()); - doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP), - anyLong(), anyInt()); - - final long time = spc.getProcessForegroundTimeMs(mUid, 1000); - - assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS); - } - - private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah, - int uidCode, boolean isUidNull, ScreenPowerCalculator spc) { - final BatterySipper sipper = mock(BatterySipper.class); - sipper.drainType = BatterySipper.DrainType.APP; - sipper.totalPowerMah = totalPowerMah; - doReturn(uidCode).when(sipper).getUid(); - if (!isUidNull) { - final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS); - doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid), anyLong()); - doReturn(uidCode).when(uid).getUid(); - sipper.uidObj = uid; - } - - return sipper; - } - -} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index be8045ddc7b2..d9b98a562ca2 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -41,6 +41,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.UidTraffic; import android.os.BatteryStats; import android.os.BluetoothBatteryStats; +import android.os.Parcel; import android.os.WakeLockStats; import android.os.WorkSource; import android.util.SparseArray; @@ -583,11 +584,42 @@ public class BatteryStatsImplTest { mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000); mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000); - BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, - BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 9000, 8000, 12000, 0); - info.setUidTraffic(ImmutableList.of( - new UidTraffic(10042, 3000, 4000), - new UidTraffic(10043, 5000, 8000))); + + + final Parcel uidTrafficParcel1 = Parcel.obtain(); + final Parcel uidTrafficParcel2 = Parcel.obtain(); + + uidTrafficParcel1.writeInt(10042); + uidTrafficParcel1.writeLong(3000); + uidTrafficParcel1.writeLong(4000); + uidTrafficParcel1.setDataPosition(0); + uidTrafficParcel2.writeInt(10043); + uidTrafficParcel2.writeLong(5000); + uidTrafficParcel2.writeLong(8000); + uidTrafficParcel2.setDataPosition(0); + + List<UidTraffic> uidTrafficList = ImmutableList.of( + UidTraffic.CREATOR.createFromParcel(uidTrafficParcel1), + UidTraffic.CREATOR.createFromParcel(uidTrafficParcel2)); + + final Parcel btActivityEnergyInfoParcel = Parcel.obtain(); + btActivityEnergyInfoParcel.writeLong(1000); + btActivityEnergyInfoParcel.writeInt( + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE); + btActivityEnergyInfoParcel.writeLong(9000); + btActivityEnergyInfoParcel.writeLong(8000); + btActivityEnergyInfoParcel.writeLong(12000); + btActivityEnergyInfoParcel.writeLong(0); + btActivityEnergyInfoParcel.writeTypedList(uidTrafficList); + btActivityEnergyInfoParcel.setDataPosition(0); + + BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR + .createFromParcel(btActivityEnergyInfoParcel); + + uidTrafficParcel1.recycle(); + uidTrafficParcel2.recycle(); + btActivityEnergyInfoParcel.recycle(); + mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000); BluetoothBatteryStats stats = diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 92c2d43ea2a3..ace39fb74b8f 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -30,7 +30,6 @@ import org.junit.runners.Suite; BatteryStatsCounterTest.class, BatteryStatsDualTimerTest.class, BatteryStatsDurationTimerTest.class, - BatteryStatsHelperTest.class, BatteryStatsHistoryIteratorTest.class, BatteryStatsHistoryTest.class, BatteryStatsImplTest.class, @@ -69,7 +68,6 @@ import org.junit.runners.Suite; LongSamplingCounterTest.class, LongSamplingCounterArrayTest.class, MobileRadioPowerCalculatorTest.class, - PowerCalculatorTest.class, PowerProfileTest.class, ScreenPowerCalculatorTest.class, SensorPowerCalculatorTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index ed035e5166b3..448f6664c2fa 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -24,6 +24,7 @@ import android.bluetooth.UidTraffic; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStatsQuery; +import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; import android.os.WorkSource; @@ -37,6 +38,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + @RunWith(AndroidJUnit4.class) @SmallTest @SuppressWarnings("GuardedBy") @@ -90,11 +93,13 @@ public class BluetoothPowerCalculatorTest { uid.setProcessStateForTest( BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); - BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, - BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); - info1.setUidTraffic(ImmutableList.of( - new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), - new UidTraffic(APP_UID, 3000, 4000))); + + List<UidTraffic> trafficList1 = ImmutableList.of( + createUidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + createUidTraffic(APP_UID, 3000, 4000)); + BluetoothActivityEnergyInfo info1 = createBtEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000, + trafficList1); batteryStats.updateBluetoothStateLocked(info1, 0/*1_000_000*/, 2000, 2000); @@ -102,11 +107,14 @@ public class BluetoothPowerCalculatorTest { uid.setProcessStateForTest( BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); - BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, - BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); - info2.setUidTraffic(ImmutableList.of( - new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), - new UidTraffic(APP_UID, 7000, 8000))); + + List<UidTraffic> trafficList2 = ImmutableList.of( + createUidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + createUidTraffic(APP_UID, 7000, 8000)); + BluetoothActivityEnergyInfo info2 = createBtEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000, + trafficList2); + batteryStats.updateBluetoothStateLocked(info2, 0 /*5_000_000 */, 4000, 4000); @@ -202,11 +210,14 @@ public class BluetoothPowerCalculatorTest { uid.setProcessStateForTest( BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); - BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000, - BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); - info1.setUidTraffic(ImmutableList.of( - new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), - new UidTraffic(APP_UID, 3000, 4000))); + + List<UidTraffic> trafficList1 = ImmutableList.of( + createUidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + createUidTraffic(APP_UID, 3000, 4000)); + BluetoothActivityEnergyInfo info1 = createBtEnergyInfo(2000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000, + trafficList1); + batteryStats.updateBluetoothStateLocked(info1, 1_000_000, 2000, 2000); @@ -214,11 +225,13 @@ public class BluetoothPowerCalculatorTest { uid.setProcessStateForTest( BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); - BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000, - BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000); - info2.setUidTraffic(ImmutableList.of( - new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000), - new UidTraffic(APP_UID, 7000, 8000))); + List<UidTraffic> trafficList2 = ImmutableList.of( + createUidTraffic(Process.BLUETOOTH_UID, 5000, 6000), + createUidTraffic(APP_UID, 7000, 8000)); + BluetoothActivityEnergyInfo info2 = createBtEnergyInfo(4000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000, + trafficList2); + batteryStats.updateBluetoothStateLocked(info2, 5_000_000, 4000, 4000); @@ -280,12 +293,15 @@ public class BluetoothPowerCalculatorTest { } private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) { - final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, + List<UidTraffic> trafficList = ImmutableList.of( + createUidTraffic(Process.BLUETOOTH_UID, 1000, 2000), + createUidTraffic(APP_UID, 3000, 4000)); + + + final BluetoothActivityEnergyInfo info = createBtEnergyInfo(1000, BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0, - reportedEnergyUc); - info.setUidTraffic(ImmutableList.of( - new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000), - new UidTraffic(APP_UID, 3000, 4000))); + reportedEnergyUc, trafficList); + mStatsRule.getBatteryStats().updateBluetoothStateLocked(info, consumedEnergyUc, 1000, 1000); } @@ -304,4 +320,34 @@ public class BluetoothPowerCalculatorTest { BatteryConsumer.POWER_COMPONENT_BLUETOOTH); assertThat(usageDurationMillis).isEqualTo(durationMs); } + + private UidTraffic createUidTraffic(int uid, long traffic1, long traffic2) { + final Parcel uidTrafficParcel = Parcel.obtain(); + uidTrafficParcel.writeInt(uid); + uidTrafficParcel.writeLong(traffic1); + uidTrafficParcel.writeLong(traffic2); + uidTrafficParcel.setDataPosition(0); + + UidTraffic traffic = UidTraffic.CREATOR.createFromParcel(uidTrafficParcel); + uidTrafficParcel.recycle(); + return traffic; + } + + private BluetoothActivityEnergyInfo createBtEnergyInfo(long timestamp, int stackState, + long txTime, long rxTime, long idleTime, long energyUsed, List<UidTraffic> traffic) { + final Parcel btActivityEnergyInfoParcel = Parcel.obtain(); + btActivityEnergyInfoParcel.writeLong(timestamp); + btActivityEnergyInfoParcel.writeInt(stackState); + btActivityEnergyInfoParcel.writeLong(txTime); + btActivityEnergyInfoParcel.writeLong(rxTime); + btActivityEnergyInfoParcel.writeLong(idleTime); + btActivityEnergyInfoParcel.writeLong(energyUsed); + btActivityEnergyInfoParcel.writeTypedList(traffic); + btActivityEnergyInfoParcel.setDataPosition(0); + + BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR + .createFromParcel(btActivityEnergyInfoParcel); + btActivityEnergyInfoParcel.recycle(); + return info; + } } diff --git a/core/tests/coretests/src/com/android/internal/os/PowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/PowerCalculatorTest.java deleted file mode 100644 index 4bd5724ed9ea..000000000000 --- a/core/tests/coretests/src/com/android/internal/os/PowerCalculatorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - */ - -package com.android.internal.os; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.os.BatteryStats; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import junit.framework.TestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class PowerCalculatorTest extends TestCase { - private static final long US_IN_HR = 1000L * 1000L * 60L * 60L; - - @Mock - private PowerProfile mPowerProfile; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - /** Test {@link MediaPowerCalculator#calculateApp} */ - @Test - public void testMediaPowerCalculator() { - when(mPowerProfile.getAveragePower(PowerProfile.POWER_AUDIO)).thenReturn(12.0); - when(mPowerProfile.getAveragePower(PowerProfile.POWER_VIDEO)).thenReturn(25.0); - - BatteryStats.Uid u = mock(BatteryStats.Uid.class); - BatteryStats.Timer audioTimer = mock(BatteryStats.Timer.class); - when(u.getAudioTurnedOnTimer()).thenReturn(audioTimer); - when(audioTimer.getTotalTimeLocked(2L * US_IN_HR, 0)).thenReturn(2L * US_IN_HR); - BatteryStats.Timer videoTimer = mock(BatteryStats.Timer.class); - when(u.getVideoTurnedOnTimer()).thenReturn(videoTimer); - when(videoTimer.getTotalTimeLocked(2L * US_IN_HR, 0)).thenReturn(1L * US_IN_HR); - - MediaPowerCalculator mediaPowerCalculator = new MediaPowerCalculator(mPowerProfile); - BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0); - - mediaPowerCalculator.calculate(List.of(app), null, 2L * US_IN_HR, 2L * US_IN_HR, 0, null); - assertEquals(49.0, app.sumPower()); - } -} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2d5f8335c8ff..ac5daf09c6f9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -508,6 +508,8 @@ applications that come with the platform <permission name="android.permission.MANAGE_APP_HIBERNATION"/> <!-- Permission required for CTS test - ResourceObserverNativeTest --> <permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" /> + <!-- Permission required for CTS test - MediaCodecResourceTest --> + <permission name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" /> <!-- Permission required for CTS test - CtsAlarmManagerTestCases --> <permission name="android.permission.SCHEDULE_PRIORITIZED_ALARM" /> <!-- Permission required for CTS test - SystemMediaRouter2Test --> @@ -547,17 +549,6 @@ applications that come with the platform <permission name="android.permission.INTERACT_ACROSS_USERS"/> </privapp-permissions> - <privapp-permissions package="com.android.traceur"> - <!-- Permissions required to receive BUGREPORT_STARTED intent --> - <permission name="android.permission.DUMP"/> - <!-- Permissions required to start/stop tracing --> - <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> - <!-- Permissions required for quick settings tile --> - <permission name="android.permission.STATUS_BAR"/> - <!-- Permissions required to query Betterbug --> - <permission name="android.permission.QUERY_ALL_PACKAGES"/> - </privapp-permissions> - <privapp-permissions package="com.android.tv"> <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/> <permission name="android.permission.DVB_DEVICE"/> diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index a9e730d58e65..2678c79d1454 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -39,6 +39,8 @@ public final class BLASTBufferQueue { private static native long nativeGetLastAcquiredFrameNum(long ptr); private static native void nativeApplyPendingTransactions(long ptr, long frameNumber); private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr); + private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr, + long frameNumber); /** Create a new connection with the surface flinger. */ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, @@ -159,4 +161,17 @@ public final class BLASTBufferQueue { public boolean isSameSurfaceControl(SurfaceControl sc) { return nativeIsSameSurfaceControl(mNativeObject, sc.mNativeObject); } + + /** + * Get any transactions that were passed to {@link #mergeWithNextTransaction} with the + * specified frameNumber. This is intended to ensure transactions don't get stuck as pending + * if the specified frameNumber is never drawn. + * + * @param frameNumber The frameNumber used to determine which transactions to apply. + * @return a Transaction that contains the merge of all the transactions that were sent to + * mergeWithNextTransaction + */ + public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) { + return nativeGatherPendingTransactions(mNativeObject, frameNumber); + } } diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index dde757b4eb01..3ec5b9cc7dae 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -29,7 +29,7 @@ public class PixelFormat { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565}) + @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGBA_1010102, RGB_888, RGB_565, R_8}) public @interface Format { } // NOTE: these constants must match the values from graphics/common/x.x/types.hal @@ -93,6 +93,9 @@ public class PixelFormat { /** @hide */ public static final int HSV_888 = 0x37; + /** @hide */ + public static final int R_8 = 0x38; + /** * @deprecated use {@link android.graphics.ImageFormat#JPEG * ImageFormat.JPEG} instead. @@ -142,6 +145,10 @@ public class PixelFormat { info.bitsPerPixel = 64; info.bytesPerPixel = 8; break; + case R_8: + info.bitsPerPixel = 8; + info.bytesPerPixel = 1; + break; default: throw new IllegalArgumentException("unknown pixel format " + format); } @@ -235,6 +242,8 @@ public class PixelFormat { return "HSV_888"; case JPEG: return "JPEG"; + case R_8: + return "R_8"; default: return Integer.toString(format); } diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index ee41148a7686..ee0d64727cff 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -290,7 +290,7 @@ public class SurfaceTexture { * context at a time. * * @param texName The name of the OpenGL ES texture that will be created. This texture name - * must be unusued in the OpenGL ES context that is current on the calling thread. + * must be unused in the OpenGL ES context that is current on the calling thread. */ public void attachToGLContext(int texName) { int err = nativeAttachToGLContext(texName); diff --git a/keystore/OWNERS b/keystore/OWNERS index a63ca46df2a6..7ab9d761e236 100644 --- a/keystore/OWNERS +++ b/keystore/OWNERS @@ -1,4 +1,4 @@ +eranm@google.com jbires@google.com jdanis@google.com -robbarnes@google.com swillden@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 9a6df23ca971..4c505f6583fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -17,6 +17,7 @@ package com.android.wm.shell.back; import android.view.MotionEvent; +import android.window.BackEvent; import com.android.wm.shell.common.annotations.ExternalThread; @@ -29,7 +30,7 @@ public interface BackAnimation { /** * Called when a {@link MotionEvent} is generated by a back gesture. */ - void onBackMotion(MotionEvent event); + void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge); /** * Sets whether the back gesture is past the trigger threshold or not. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index a5140c3aafff..32ac43da951c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -36,6 +36,7 @@ import android.os.SystemProperties; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.window.BackEvent; import android.window.BackNavigationInfo; import android.window.IOnBackInvokedCallback; @@ -127,8 +128,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } @Override - public void onBackMotion(MotionEvent event) { - mShellExecutor.execute(() -> onMotionEvent(event)); + public void onBackMotion(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { + mShellExecutor.execute(() -> onMotionEvent(event, swipeEdge)); } @Override @@ -183,12 +184,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} */ - public void onMotionEvent(MotionEvent event) { + public void onMotionEvent(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { initAnimation(event); } else if (action == MotionEvent.ACTION_MOVE) { - onMove(event); + onMove(event, swipeEdge); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { onGestureFinished(); } @@ -264,7 +265,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransaction.setVisibility(screenshotSurface, true); } - private void onMove(MotionEvent event) { + private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { if (!mBackGestureStarted || mBackNavigationInfo == null) { return; } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 99f7e236ee3f..0bc6936c1eb4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -95,7 +95,7 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } const val FIND_OBJECT_TIMEOUT = 2000L diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt index a928bbdb288f..f6abc75037ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt @@ -28,6 +28,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -44,11 +47,16 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val displaySize = DisplayMetrics() + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = buildTransition { setup { @@ -68,7 +76,7 @@ class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(tes @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt new file mode 100644 index 000000000000..dd744b3c45ab --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreenShellTransit.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice + +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before + +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class DismissBubbleScreenShellTransit( + testSpec: FlickerTestParameter +) : DismissBubbleScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt index af629cc9f8ee..2ec743c10413 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -24,6 +24,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -42,7 +45,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } override val transition: FlickerBuilder.() -> Unit get() = buildTransition { @@ -61,7 +69,7 @@ class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt new file mode 100644 index 000000000000..d92ec7781005 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreenShellTransit.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class ExpandBubbleScreenShellTransit( + testSpec: FlickerTestParameter +) : ExpandBubbleScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index 64636be1195f..a20a20125bf4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -22,6 +22,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -39,7 +42,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } override val transition: FlickerBuilder.() -> Unit get() = buildTransition { @@ -51,7 +59,7 @@ class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt new file mode 100644 index 000000000000..9350868a99f4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreenShellTransit.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class LaunchBubbleScreenShellTransit( + testSpec: FlickerTestParameter +) : LaunchBubbleScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt index add11c10d75f..8d1e315e2d5e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -25,6 +25,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -41,7 +44,12 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @Group4 -class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { +open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } override val transition: FlickerBuilder.() -> Unit get() = buildTransition { @@ -69,7 +77,7 @@ class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test @Presubmit @Test - fun testAppIsAlwaysVisible() { + open fun testAppIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(testApp.component) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt new file mode 100644 index 000000000000..ddebb6fed636 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.bubble + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +@FlakyTest(bugId = 217777115) +class MultiBubblesScreenShellTransit( + testSpec: FlickerTestParameter +) : MultiBubblesScreen(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index db94de238a5d..467cadc7e6e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -71,24 +71,6 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test @@ -205,7 +187,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index afe64e3d4abc..accb524d3de1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -224,7 +224,7 @@ class EnterPipToOtherOrientationTest( fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 3a9a0705908d..931d060be33d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -98,7 +98,7 @@ class ExitPipViaExpandButtonClickTest( @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 03c8929f9919..e00d7491839f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -78,24 +78,6 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test @@ -117,7 +99,7 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 976b7c6980a1..5214daa0ec44 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -67,24 +67,6 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test @@ -107,7 +89,7 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 906123914731..332bba6ad6ef 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -93,7 +93,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 8d14f70357b1..07c3b1524d36 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -173,7 +173,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index e4150241d42c..e91bef10a9e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -96,7 +96,7 @@ class MovePipDownShelfHeightChangeTest( @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index 4a4c46c596a2..7e66cd3718e4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -96,7 +96,7 @@ class MovePipUpShelfHeightChangeTest( @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 1d61ab48d663..7a1d61904236 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -31,7 +31,7 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.ImeAppHelper import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -47,9 +47,14 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) + @Before + open fun before() { + assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = buildTransition(eachRun = false) { setup { @@ -77,25 +82,14 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } - - @FlakyTest(bugId = 214452854) - @Test - fun statusBarLayerRotatesScales_shellTransit() { - assumeTrue(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() /** * Ensure the pip window remains visible throughout any keyboard interactions */ @Presubmit @Test - fun pipInVisibleBounds() { + open fun pipInVisibleBounds() { testSpec.assertWmVisibleRegion(pipApp.component) { val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) coversAtMost(displayBounds) @@ -107,7 +101,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) */ @Presubmit @Test - fun pipIsAboveAppWindow() { + open fun pipIsAboveAppWindow() { testSpec.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(FlickerComponentName.IME, pipApp.component) } @@ -121,7 +115,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt new file mode 100644 index 000000000000..1a21d32f568c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.pip + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 217777115) +class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) { + + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } + + @FlakyTest(bugId = 214452854) + @Test + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index b2b50ade78a9..27873bbea8ef 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -27,10 +27,13 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.wm.shell.flicker.helpers.FixedAppHelper +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -61,11 +64,16 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val fixedApp = FixedAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation) private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation) + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = buildTransition(eachRun = false) { setup { @@ -182,7 +190,7 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance().getConfigRotationTests( supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), - repetitions = 5) + repetitions = 3) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt new file mode 100644 index 000000000000..a017f56af5bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.pip + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 217777115) +class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 8dd91048d29b..d35654e632bb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -26,9 +26,12 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP +import org.junit.Assume +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -44,12 +47,17 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -class SetRequestedOrientationWhilePinnedTest( +open class SetRequestedOrientationWhilePinnedTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) + @Before + open fun before() { + Assume.assumeFalse(isShellTransitionsEnabled) + } + override val transition: FlickerBuilder.() -> Unit get() = { setupAndTeardown(this) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt new file mode 100644 index 000000000000..36e28049864f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTestShellTransit.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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.wm.shell.flicker.pip + +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +@FlakyTest(bugId = 217777115) +class SetRequestedOrientationWhilePinnedTestShellTransit( + testSpec: FlickerTestParameter +) : SetRequestedOrientationWhilePinnedTest(testSpec) { + @Before + override fun before() { + Assume.assumeTrue(isShellTransitionsEnabled) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 21ced0dc5981..b11f9103e16c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -30,6 +30,7 @@ import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.window.BackEvent; import android.window.BackNavigationInfo; import androidx.test.filters.SmallTest; @@ -94,7 +95,9 @@ public class BackAnimationControllerTest { SurfaceControl screenshotSurface = new SurfaceControl(); HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); - mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); + mController.onMotionEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), + BackEvent.EDGE_LEFT); verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); verify(mTransaction).setVisibility(screenshotSurface, true); verify(mTransaction).apply(); @@ -106,8 +109,12 @@ public class BackAnimationControllerTest { SurfaceControl screenshotSurface = new SurfaceControl(); HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer); - mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0)); - mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0)); + mController.onMotionEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), + BackEvent.EDGE_LEFT); + mController.onMotionEvent( + MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0), + BackEvent.EDGE_LEFT); verify(mTransaction).setPosition(topWindowLeash, 100, 100); verify(mTransaction, atLeastOnce()).apply(); } diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp index 8cb451528396..2757c3952dbb 100644 --- a/libs/hwui/effects/StretchEffect.cpp +++ b/libs/hwui/effects/StretchEffect.cpp @@ -227,7 +227,7 @@ sk_sp<SkShader> StretchEffect::getShader(float width, float height, mBuilder->uniform("viewportWidth").set(&width, 1); mBuilder->uniform("viewportHeight").set(&height, 1); - auto result = mBuilder->makeShader(nullptr, false); + auto result = mBuilder->makeShader(); mBuilder->child(CONTENT_TEXTURE) = nullptr; return result; } diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 899c7d4d75e2..0bbd8a8cf97c 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -264,7 +264,7 @@ static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) { static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr) { SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); - sk_sp<SkShader> shader = builder->makeShader(matrix, false); + sk_sp<SkShader> shader = builder->makeShader(matrix); ThrowIAE_IfNull(env, shader); return reinterpret_cast<jlong>(shader.release()); } diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h index d173782fd880..9cf93e66cfbe 100644 --- a/libs/hwui/pipeline/skia/AnimatedDrawables.h +++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h @@ -110,7 +110,7 @@ public: const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI; setUniform2f(effectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3)); - params.paint->value.setShader(effectBuilder.makeShader(nullptr, false)); + params.paint->value.setShader(effectBuilder.makeShader()); canvas->drawCircle(params.x->value, params.y->value, params.radius->value, params.paint->value); } diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index 7c57bd52d6fe..2fba13c3cfea 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -98,7 +98,7 @@ static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); } - return effectBuilder.makeShader(nullptr, false); + return effectBuilder.makeShader(); } static bool isHdrDataspace(ui::Dataspace dataspace) { diff --git a/media/Android.bp b/media/Android.bp index 5aedcfbc22e9..2f9c5203b462 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -103,6 +103,11 @@ aidl_interface { }, java: { sdk_version: "module_current", + min_sdk_version: "29", + apex_available: [ + "//apex_available:platform", + "com.android.car.framework", + ], }, ndk: { vndk: { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index cdc31631637e..3887372d00b5 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -91,6 +91,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * AudioManager provides access to volume and ringer mode control. @@ -8342,6 +8343,106 @@ public class AudioManager { } } + /** + * Add UID's that can be considered as assistant. + * + * @param assistantUids UID's of the services that can be considered as assistant. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void addAssistantServicesUids(@NonNull List<Integer> assistantUids) { + try { + getService().addAssistantServicesUids(assistantUids.stream() + .mapToInt(Integer::intValue).toArray()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove UID's that can be considered as assistant. + * + * @param assistantUids UID'S of the services that should be remove. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void removeAssistantServicesUids(@NonNull List<Integer> assistantUids) { + try { + getService().removeAssistantServicesUids(assistantUids.stream() + .mapToInt(Integer::intValue).toArray()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the list of assistants UIDs that been added with the + * {@link #addAssistantServicesUids(List)} (List)} and not yet removed with + * {@link #removeAssistantServicesUids(List)} + * + * @return list of assistants UID's + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<Integer> getAssistantServicesUids() { + try { + int[] uids = getService().getAssistantServicesUids(); + return Arrays.stream(uids).boxed().collect(Collectors.toList()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets UID's that can be considered as active assistant. Calling the API with a new list will + * overwrite previous list. If the list of UIDs is empty then no UID will be considered active. + * In this manner calling the API with an empty list will remove all UID's previously set. + * + * @param assistantUids UID'S of the services that can be considered active assistant. Can be + * an empty list, for this no UID will be considered active. + * + * <p> Note that during audio service crash reset and after boot up the list of active assistant + * UID's will be reset to an empty list (i.e. no UID will be considered as an active assistant). + * Just after user switch the list of active assistant will also reset to empty. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setActiveAssistantServiceUids(@NonNull List<Integer> assistantUids) { + try { + getService().setActiveAssistantServiceUids(assistantUids.stream() + .mapToInt(Integer::intValue).toArray()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the list of active assistant UIDs last set with the + * {@link #setActiveAssistantServiceUids(List)} + * + * @return list of active assistants UID's + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public @NonNull List<Integer> getActiveAssistantServicesUids() { + try { + int[] uids = getService().getActiveAssistantServiceUids(); + return Arrays.stream(uids).boxed().collect(Collectors.toList()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final Object mMuteAwaitConnectionListenerLock = new Object(); @GuardedBy("mMuteAwaitConnectionListenerLock") diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index cb887f2d523d..c2632458435a 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -38,18 +38,27 @@ public abstract class AudioManagerInternal { public abstract void updateRingerModeAffectedStreamsInternal(); + public abstract void setAccessibilityServiceUids(IntArray uids); + /** - * Notify the UID of the currently active {@link android.service.voice.HotwordDetectionService}. + * Add the UID for a new assistant service * - * <p>The caller is expected to take care of any performance implications, e.g. by using a - * background thread to call this method.</p> + * @param uid UID of the newly available assistants + */ + public abstract void addAssistantServiceUid(int uid); + + /** + * Remove the UID for an existing assistant service * - * @param uid UID of the currently active service or {@link android.os.Process#INVALID_UID} if - * none. + * @param uid UID of the currently available assistant */ - public abstract void setHotwordDetectionServiceUid(int uid); + public abstract void removeAssistantServiceUid(int uid); - public abstract void setAccessibilityServiceUids(IntArray uids); + /** + * Set the currently active assistant service UIDs + * @param activeUids active UIDs of the assistant service + */ + public abstract void setActiveAssistantServicesUids(IntArray activeUids); /** * Called by {@link com.android.server.inputmethod.InputMethodManagerService} to notify the UID diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 536b4ad71285..6cacebb433a6 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1856,16 +1856,15 @@ public class AudioSystem /** * @hide - * Communicate UID of active assistant to audio policy service. + * Communicate UIDs of the active assistant to audio policy service. */ - public static native int setAssistantUid(int uid); + public static native int setActiveAssistantServicesUids(int[] uids); /** - * Communicate UID of the current {@link android.service.voice.HotwordDetectionService} to audio - * policy service. * @hide + * Communicate UIDs of assistant to audio policy service. */ - public static native int setHotwordDetectionServiceUid(int uid); + public static native int setAssistantServicesUids(int[] uids); /** * @hide diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index fec14def618c..8ba3539edc58 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -465,4 +465,19 @@ interface IAudioService { List<AudioFocusInfo> getFocusStack(); boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + void addAssistantServicesUids(in int[] assistantUID); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + void removeAssistantServicesUids(in int[] assistantUID); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + void setActiveAssistantServiceUids(in int[] activeUids); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + int[] getAssistantServicesUids(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)") + int[] getActiveAssistantServiceUids(); } diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 939b679676aa..4563259c31f2 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,9 +16,12 @@ package android.media; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.ImageFormat; import android.graphics.Rect; @@ -1934,12 +1937,41 @@ final public class MediaCodec { @NonNull public static MediaCodec createByCodecName(@NonNull String name) throws IOException { - return new MediaCodec( - name, false /* nameIsType */, false /* unused */); + return new MediaCodec(name, false /* nameIsType */, false /* encoder */); } - private MediaCodec( - @NonNull String name, boolean nameIsType, boolean encoder) { + /** + * This is the same as createByCodecName, but allows for instantiating a codec on behalf of a + * client process. This is used for system apps or system services that create MediaCodecs on + * behalf of other processes and will reclaim resources as necessary from processes with lower + * priority than the client process, rather than processes with lower priority than the system + * app or system service. Likely to be used with information obtained from + * {@link android.media.MediaCodecList}. + * @param name + * @param clientPid + * @param clientUid + * @throws IOException if the codec cannot be created. + * @throws IllegalArgumentException if name is not valid. + * @throws NullPointerException if name is null. + * @throws SecurityException if the MEDIA_RESOURCE_OVERRIDE_PID permission is not granted. + * + * @hide + */ + @NonNull + @SystemApi + @RequiresPermission(Manifest.permission.MEDIA_RESOURCE_OVERRIDE_PID) + public static MediaCodec createByCodecNameForClient(@NonNull String name, int clientPid, + int clientUid) throws IOException { + return new MediaCodec(name, false /* nameIsType */, false /* encoder */, clientPid, + clientUid); + } + + private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder) { + this(name, nameIsType, encoder, -1 /* pid */, -1 /* uid */); + } + + private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder, int pid, + int uid) { Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); @@ -1957,7 +1989,7 @@ final public class MediaCodec { // save name used at creation mNameAtCreation = nameIsType ? null : name; - native_setup(name, nameIsType, encoder); + native_setup(name, nameIsType, encoder, pid, uid); } private String mNameAtCreation; @@ -4991,7 +5023,7 @@ final public class MediaCodec { private static native final void native_init(); private native final void native_setup( - @NonNull String name, boolean nameIsType, boolean encoder); + @NonNull String name, boolean nameIsType, boolean encoder, int pid, int uid); private native final void native_finalize(); diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 13c1498604ba..7d57734defc5 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -60,21 +60,21 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; /** - * MediaRouter allows applications to control the routing of media channels + * This API is not recommended for new applications. Use the + * <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> + * <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router + * Library</a> for consistent behavior across all devices. + * + * <p>MediaRouter allows applications to control the routing of media channels * and streams from the current device to external speakers and destination devices. * * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String) * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE * Context.MEDIA_ROUTER_SERVICE}. * - * <p>The media router API is not thread-safe; all interactions with it must be - * done from the main thread of the process.</p> - * - * <p> - * We recommend using {@link android.media.MediaRouter2} APIs for new applications. - * </p> + * <p>This API is not thread-safe; all interactions with it must be done from the main thread of the + * process. */ -//TODO: Link androidx.media2.MediaRouter when we are ready. @SystemService(Context.MEDIA_ROUTER_SERVICE) public class MediaRouter { private static final String TAG = "MediaRouter"; diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index f8c47e83595b..311476cbd489 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -52,13 +52,13 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** - * This API is not generally intended for third party application developers. - * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> - <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router + * This API is not generally intended for third party application developers. Use the + * <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> + * <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router * Library</a> for consistent behavior across all devices. * - * Media Router 2 allows applications to control the routing of media channels - * and streams from the current device to remote speakers and devices. + * <p>MediaRouter2 allows applications to control the routing of media channels and streams from + * the current device to remote speakers and devices. */ // TODO(b/157873330): Add method names at the beginning of log messages. (e.g. selectRoute) // Not only MediaRouter2, but also to service / manager / provider. diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index 955ae3ca28fb..37050df160e3 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -407,7 +407,7 @@ public final class MediaController { /** * Get the session owner's package name. * - * @return The package name of of the session owner. + * @return The package name of the session owner. */ public String getPackageName() { if (mPackageName == null) { diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 73e96a2e6c3e..96809bda7c31 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1877,6 +1877,7 @@ public final class TvInputManager { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase, @Nullable String sessionId) { return getClientPriorityInternal(useCase, sessionId); diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp index 4d6739e8fa52..31e18178aee5 100644 --- a/media/jni/android_media_ImageReader.cpp +++ b/media/jni/android_media_ImageReader.cpp @@ -623,7 +623,7 @@ static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image, Image_setBufferItem(env, image, buffer); env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp, static_cast<jlong>(buffer->mTimestamp)); - env->SetLongField(image, gSurfaceImageClassInfo.mDataSpace, + env->SetIntField(image, gSurfaceImageClassInfo.mDataSpace, static_cast<jint>(buffer->mDataSpace)); auto transform = buffer->mTransform; if (buffer->mTransformToDisplayInverse) { diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp index 0f88afbf9a85..6c6fccb59216 100644 --- a/media/jni/android_media_ImageWriter.cpp +++ b/media/jni/android_media_ImageWriter.cpp @@ -901,7 +901,7 @@ static void Image_setNativeContext(JNIEnv* env, jobject thiz, env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd)); - env->SetLongField(thiz, gSurfaceImageClassInfo.mDataSpace, dataSpace); + env->SetIntField(thiz, gSurfaceImageClassInfo.mDataSpace, dataSpace); } static void Image_unlockIfLocked(JNIEnv* env, jobject thiz) { diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index f6944823feb5..c8d2d1ee621f 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -202,7 +202,7 @@ static const void *sRefBaseOwner; JMediaCodec::JMediaCodec( JNIEnv *env, jobject thiz, - const char *name, bool nameIsType, bool encoder) + const char *name, bool nameIsType, bool encoder, int pid, int uid) : mClass(NULL), mObject(NULL) { jclass clazz = env->GetObjectClass(thiz); @@ -220,12 +220,12 @@ JMediaCodec::JMediaCodec( ANDROID_PRIORITY_VIDEO); if (nameIsType) { - mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus); + mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus, pid, uid); if (mCodec == nullptr || mCodec->getName(&mNameAtCreation) != OK) { mNameAtCreation = "(null)"; } } else { - mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus); + mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus, pid, uid); mNameAtCreation = name; } CHECK((mCodec != NULL) != (mInitStatus != OK)); @@ -3136,7 +3136,7 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { static void android_media_MediaCodec_native_setup( JNIEnv *env, jobject thiz, - jstring name, jboolean nameIsType, jboolean encoder) { + jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) { if (name == NULL) { jniThrowException(env, "java/lang/NullPointerException", NULL); return; @@ -3148,24 +3148,33 @@ static void android_media_MediaCodec_native_setup( return; } - sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder); + sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder, pid, uid); const status_t err = codec->initCheck(); if (err == NAME_NOT_FOUND) { // fail and do not try again. jniThrowException(env, "java/lang/IllegalArgumentException", - String8::format("Failed to initialize %s, error %#x", tmp, err)); + String8::format("Failed to initialize %s, error %#x (NAME_NOT_FOUND)", tmp, err)); env->ReleaseStringUTFChars(name, tmp); return; - } if (err == NO_MEMORY) { + } + if (err == NO_MEMORY) { throwCodecException(env, err, ACTION_CODE_TRANSIENT, - String8::format("Failed to initialize %s, error %#x", tmp, err)); + String8::format("Failed to initialize %s, error %#x (NO_MEMORY)", tmp, err)); + env->ReleaseStringUTFChars(name, tmp); + return; + } + if (err == PERMISSION_DENIED) { + jniThrowException(env, "java/lang/SecurityException", + String8::format("Failed to initialize %s, error %#x (PERMISSION_DENIED)", tmp, + err)); env->ReleaseStringUTFChars(name, tmp); return; - } else if (err != OK) { + } + if (err != OK) { // believed possible to try again jniThrowException(env, "java/io/IOException", - String8::format("Failed to find matching codec %s, error %#x", tmp, err)); + String8::format("Failed to find matching codec %s, error %#x (?)", tmp, err)); env->ReleaseStringUTFChars(name, tmp); return; } @@ -3174,7 +3183,7 @@ static void android_media_MediaCodec_native_setup( codec->registerSelf(); - setMediaCodec(env,thiz, codec); + setMediaCodec(env, thiz, codec); } static void android_media_MediaCodec_native_finalize( @@ -3478,7 +3487,7 @@ static const JNINativeMethod gMethods[] = { { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, - { "native_setup", "(Ljava/lang/String;ZZ)V", + { "native_setup", "(Ljava/lang/String;ZZII)V", (void *)android_media_MediaCodec_native_setup }, { "native_finalize", "()V", diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index ee456c9ba82d..616c31b29157 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -55,7 +55,7 @@ using hardware::cas::native::V1_0::IDescrambler; struct JMediaCodec : public AHandler { JMediaCodec( JNIEnv *env, jobject thiz, - const char *name, bool nameIsType, bool encoder); + const char *name, bool nameIsType, bool encoder, int pid, int uid); status_t initCheck() const; diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index b7beb6e1a6a8..3009a36bae2c 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -205,6 +205,7 @@ LIBANDROID { ASensorManager_destroyEventQueue; ASensorManager_getDefaultSensor; ASensorManager_getDefaultSensorEx; # introduced=21 + ASensorManager_getDynamicSensorList; # introduced=33 ASensorManager_getInstance; ASensorManager_getInstanceForPackage; # introduced=26 ASensorManager_getSensorList; diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp index 63082fd70bc6..968de34f5d69 100644 --- a/native/android/sensor.cpp +++ b/native/android/sensor.cpp @@ -83,6 +83,16 @@ int ASensorManager_getSensorList(ASensorManager* manager, ASensorList* list) { return c; } +ssize_t ASensorManager_getDynamicSensorList(ASensorManager* manager, ASensorList* list) { + RETURN_IF_MANAGER_IS_NULL(android::BAD_VALUE); + Sensor const* const* l; + ssize_t c = static_cast<SensorManager*>(manager)->getDynamicSensorList(&l); + if (list) { + *list = reinterpret_cast<ASensorList>(l); + } + return c; +} + ASensor const* ASensorManager_getDefaultSensor(ASensorManager* manager, int type) { RETURN_IF_MANAGER_IS_NULL(nullptr); return static_cast<SensorManager*>(manager)->getDefaultSensor(type); diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index 221a848a5998..ca080ce4c64a 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -699,7 +699,9 @@ public class NetworkStatsManager { * @hide */ @SystemApi - @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) @NonNull public android.net.NetworkStats getMobileUidStats() { try { return mService.getUidStatsForTransport(TRANSPORT_CELLULAR); @@ -723,7 +725,9 @@ public class NetworkStatsManager { * @hide */ @SystemApi - @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) @NonNull public android.net.NetworkStats getWifiUidStats() { try { return mService.getUidStatsForTransport(TRANSPORT_WIFI); diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java index 1f67f6d654e1..1a955c4c57d7 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java @@ -16,14 +16,16 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import android.annotation.CallbackExecutor; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -33,13 +35,15 @@ import android.os.RemoteException; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.BiConsumer; /** - * A class representing the IP configuration of the Ethernet network. + * A class that manages and configures Ethernet interfaces. * * @hide */ @@ -54,11 +58,13 @@ public class EthernetManager { private final IEthernetServiceListener.Stub mServiceListener = new IEthernetServiceListener.Stub() { @Override - public void onAvailabilityChanged(String iface, boolean isAvailable) { + public void onInterfaceStateChanged(String iface, int state, int role, + IpConfiguration configuration) { synchronized (mListeners) { for (ListenerInfo li : mListeners) { li.executor.execute(() -> - li.listener.onAvailabilityChanged(iface, isAvailable)); + li.listener.onInterfaceStateChanged(iface, state, role, + configuration)); } } } @@ -68,19 +74,93 @@ public class EthernetManager { @NonNull public final Executor executor; @NonNull - public final Listener listener; + public final InterfaceStateListener listener; - private ListenerInfo(@NonNull Executor executor, @NonNull Listener listener) { + private ListenerInfo(@NonNull Executor executor, @NonNull InterfaceStateListener listener) { this.executor = executor; this.listener = listener; } } /** + * The interface is absent. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int STATE_ABSENT = 0; + + /** + * The interface is present but link is down. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int STATE_LINK_DOWN = 1; + + /** + * The interface is present and link is up. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int STATE_LINK_UP = 2; + + /** @hide */ + @IntDef(prefix = "STATE_", value = {STATE_ABSENT, STATE_LINK_DOWN, STATE_LINK_UP}) + @Retention(RetentionPolicy.SOURCE) + public @interface InterfaceState {} + + /** + * The interface currently does not have any specific role. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int ROLE_NONE = 0; + + /** + * The interface is in client mode (e.g., connected to the Internet). + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int ROLE_CLIENT = 1; + + /** + * Ethernet interface is in server mode (e.g., providing Internet access to tethered devices). + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static final int ROLE_SERVER = 2; + + /** @hide */ + @IntDef(prefix = "ROLE_", value = {ROLE_NONE, ROLE_CLIENT, ROLE_SERVER}) + @Retention(RetentionPolicy.SOURCE) + public @interface Role {} + + /** + * A listener that receives notifications about the state of Ethernet interfaces on the system. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public interface InterfaceStateListener { + /** + * Called when an Ethernet interface changes state. + * + * @param iface the name of the interface. + * @param state the current state of the interface, or {@link #STATE_ABSENT} if the + * interface was removed. + * @param role whether the interface is in the client mode or server mode. + * @param configuration the current IP configuration of the interface. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state, + @Role int role, @Nullable IpConfiguration configuration); + } + + /** * A listener interface to receive notification on changes in Ethernet. + * This has never been a supported API. Use {@link InterfaceStateListener} instead. * @hide */ - public interface Listener { + public interface Listener extends InterfaceStateListener { /** * Called when Ethernet port's availability is changed. * @param iface Ethernet interface name @@ -89,6 +169,13 @@ public class EthernetManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) void onAvailabilityChanged(String iface, boolean isAvailable); + + /** Default implementation for backwards compatibility. Only calls the legacy listener. */ + default void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state, + @Role int role, @Nullable IpConfiguration configuration) { + onAvailabilityChanged(iface, (state >= STATE_LINK_UP)); + } + } /** @@ -121,7 +208,7 @@ public class EthernetManager { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setConfiguration(String iface, IpConfiguration config) { + public void setConfiguration(@NonNull String iface, @NonNull IpConfiguration config) { try { mService.setConfiguration(iface, config); } catch (RemoteException e) { @@ -155,9 +242,8 @@ public class EthernetManager { /** * Adds a listener. + * This has never been a supported API. Use {@link #addInterfaceStateListener} instead. * - * Consider using {@link #addListener(Listener, Executor)} instead: this method uses a default - * executor that may have higher latency than a provided executor. * @param listener A {@link Listener} to add. * @throws IllegalArgumentException If the listener is null. * @hide @@ -169,6 +255,8 @@ public class EthernetManager { /** * Adds a listener. + * This has never been a supported API. Use {@link #addInterfaceStateListener} instead. + * * @param listener A {@link Listener} to add. * @param executor Executor to run callbacks on. * @throws IllegalArgumentException If the listener or executor is null. @@ -176,6 +264,28 @@ public class EthernetManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void addListener(@NonNull Listener listener, @NonNull Executor executor) { + addInterfaceStateListener(executor, listener); + } + + /** + * Listen to changes in the state of Ethernet interfaces. + * + * Adds a listener to receive notification for any state change of all existing Ethernet + * interfaces. + * <p>{@link Listener#onInterfaceStateChanged} will be triggered immediately for all + * existing interfaces upon adding a listener. The same method will be called on the + * listener every time any of the interface changes state. In particular, if an + * interface is removed, it will be called with state {@link #STATE_ABSENT}. + * <p>Use {@link #removeInterfaceStateListener} with the same object to stop listening. + * + * @param executor Executor to run callbacks on. + * @param listener A {@link Listener} to add. + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @SystemApi(client = MODULE_LIBRARIES) + public void addInterfaceStateListener(@NonNull Executor executor, + @NonNull InterfaceStateListener listener) { if (listener == null || executor == null) { throw new NullPointerException("listener and executor must not be null"); } @@ -206,15 +316,13 @@ public class EthernetManager { /** * Removes a listener. + * * @param listener A {@link Listener} to remove. - * @throws IllegalArgumentException If the listener is null. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void removeListener(@NonNull Listener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener must not be null"); - } + @SystemApi(client = MODULE_LIBRARIES) + public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) { + Objects.requireNonNull(listener); synchronized (mListeners) { mListeners.removeIf(l -> l.listener == listener); if (mListeners.isEmpty()) { @@ -228,12 +336,26 @@ public class EthernetManager { } /** + * Removes a listener. + * This has never been a supported API. Use {@link #removeInterfaceStateListener} instead. + * @param listener A {@link Listener} to remove. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void removeListener(@NonNull Listener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + removeInterfaceStateListener(listener); + } + + /** * Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface} * as Ethernet interfaces. The effects of this method apply to any test interfaces that are * already present on the system. * @hide */ - @TestApi + @SystemApi(client = MODULE_LIBRARIES) public void setIncludeTestInterfaces(boolean include) { try { mService.setIncludeTestInterfaces(include); diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl index 782fa19d9df7..6d2ba03f78d4 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl @@ -16,8 +16,11 @@ package android.net; +import android.net.IpConfiguration; + /** @hide */ oneway interface IEthernetServiceListener { - void onAvailabilityChanged(String iface, boolean isAvailable); + void onInterfaceStateChanged(String iface, int state, int role, + in IpConfiguration configuration); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java index a423783bc1ca..9cb0947b2370 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java +++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java @@ -61,7 +61,7 @@ import java.util.Objects; * Internet Protocol</a> */ @SystemService(Context.IPSEC_SERVICE) -public final class IpSecManager { +public class IpSecManager { private static final String TAG = "IpSecManager"; /** diff --git a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java index 061f323447b0..42c0044e666f 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2022 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. diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index e366ca13df79..78ba218e7552 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -1067,7 +1067,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getUidStatsForTransport(int transport) { - enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + PermissionUtils.enforceNetworkStackPermission(mContext); try { final String[] relevantIfaces = transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces; diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index f7b297461f15..17db45dcaeb3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -138,8 +138,6 @@ public class RestrictedPreferenceHelper { return true; } if (mDisabledByAppOps) { - Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU, - "Build SDK version needs >= T"); RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName, uid); return true; diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java index 2e7cfcb13d8c..9a29f2250b7e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java @@ -51,6 +51,9 @@ public class RecentAppOpsAccess { private static final int[] MICROPHONE_OPS = new int[]{ AppOpsManager.OP_RECORD_AUDIO, }; + private static final int[] CAMERA_OPS = new int[]{ + AppOpsManager.OP_CAMERA, + }; private static final String TAG = RecentAppOpsAccess.class.getSimpleName(); @@ -99,6 +102,13 @@ public class RecentAppOpsAccess { } /** + * Creates an instance of {@link RecentAppOpsAccess} for camera access. + */ + public static RecentAppOpsAccess createForCamera(Context context) { + return new RecentAppOpsAccess(context, CAMERA_OPS); + } + + /** * Fills a list of applications which queried for access recently within specified time. * Apps are sorted by recency. Apps with more recent accesses are in the front. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java index 26e3e0432fd6..484ca9321c23 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java @@ -32,7 +32,6 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.Switch; import android.widget.Toast; @@ -156,14 +155,10 @@ public class InputMethodPreference extends PrimarySwitchPreference final int iconSize = getContext().getResources().getDimensionPixelSize( R.dimen.secondary_app_icon_size); if (icon != null && iconSize > 0) { - if (isTv()) { - ViewGroup.LayoutParams params = icon.getLayoutParams(); - params.height = iconSize; - params.width = iconSize; - icon.setLayoutParams(params); - } else { - icon.setLayoutParams(new LinearLayout.LayoutParams(iconSize, iconSize)); - } + ViewGroup.LayoutParams params = icon.getLayoutParams(); + params.height = iconSize; + params.width = iconSize; + icon.setLayoutParams(params); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index dee68948a16e..6a1cee3146a2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -16,6 +16,7 @@ package com.android.settingslib.notification; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlertDialog; @@ -42,8 +43,6 @@ import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.R; @@ -72,6 +71,9 @@ public class EnableZenModeDialog { private static final int SECONDS_MS = 1000; private static final int MINUTES_MS = 60 * SECONDS_MS; + @Nullable + private final ZenModeDialogMetricsLogger mMetricsLogger; + @VisibleForTesting protected Uri mForeverId; private int mBucketIndex = -1; @@ -102,13 +104,16 @@ public class EnableZenModeDialog { } public EnableZenModeDialog(Context context, int themeResId) { - this(context, themeResId, false /* cancelIsNeutral */); + this(context, themeResId, false /* cancelIsNeutral */, + new ZenModeDialogMetricsLogger(context)); } - public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) { + public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral, + ZenModeDialogMetricsLogger metricsLogger) { mContext = context; mThemeResId = themeResId; mCancelIsNeutral = cancelIsNeutral; + mMetricsLogger = metricsLogger; } public AlertDialog createDialog() { @@ -129,17 +134,11 @@ public class EnableZenModeDialog { ConditionTag tag = getConditionTagAt(checkedId); if (isForever(tag.condition)) { - MetricsLogger.action(mContext, - MetricsProto.MetricsEvent. - NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER); + mMetricsLogger.logOnEnableZenModeForever(); } else if (isAlarm(tag.condition)) { - MetricsLogger.action(mContext, - MetricsProto.MetricsEvent. - NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM); + mMetricsLogger.logOnEnableZenModeUntilAlarm(); } else if (isCountdown(tag.condition)) { - MetricsLogger.action(mContext, - MetricsProto.MetricsEvent. - NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN); + mMetricsLogger.logOnEnableZenModeUntilCountdown(); } else { Slog.d(TAG, "Invalid manual condition: " + tag.condition); } @@ -222,8 +221,7 @@ public class EnableZenModeDialog { if (isChecked) { tag.rb.setChecked(true); if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId); - MetricsLogger.action(mContext, - MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT); + mMetricsLogger.logOnConditionSelected(); updateAlarmWarningText(tag.condition); } } @@ -435,7 +433,7 @@ public class EnableZenModeDialog { } private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { - MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up); + mMetricsLogger.logOnClickTimeButton(up); Condition newCondition = null; final int N = MINUTE_BUCKETS.length; if (mBucketIndex == -1) { diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenModeDialogMetricsLogger.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenModeDialogMetricsLogger.java new file mode 100644 index 000000000000..088a37d995ed --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenModeDialogMetricsLogger.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 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.settingslib.notification; + +import android.content.Context; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; + +/** + * Logs ui events for {@link EnableZenModeDialog}. + */ +public class ZenModeDialogMetricsLogger { + private final Context mContext; + + public ZenModeDialogMetricsLogger(Context context) { + mContext = context; + } + + /** + * User enabled DND from the QS DND dialog to last until manually turned off + */ + public void logOnEnableZenModeForever() { + MetricsLogger.action( + mContext, + MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER); + } + + /** + * User enabled DND from the QS DND dialog to last until the next alarm goes off + */ + public void logOnEnableZenModeUntilAlarm() { + MetricsLogger.action( + mContext, + MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM); + } + + /** + * User enabled DND from the QS DND dialog to last until countdown is done + */ + public void logOnEnableZenModeUntilCountdown() { + MetricsLogger.action( + mContext, + MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN); + } + + /** + * User selected an option on the DND dialog + */ + public void logOnConditionSelected() { + MetricsLogger.action( + mContext, + MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT); + } + + /** + * User increased or decreased countdown duration of DND from the DND dialog + */ + public void logOnClickTimeButton(boolean up) { + MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up); + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index a6bfc408be7e..716ee845bea6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -43,6 +43,7 @@ public class SystemSettings { Settings.System.FONT_SCALE, Settings.System.DIM_SCREEN, Settings.System.SCREEN_OFF_TIMEOUT, + Settings.System.SCREEN_OFF_TIMEOUT_DOCKED, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Settings.System.SCREEN_BRIGHTNESS_FOR_VR, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 06712cc68b89..d4302963f2d3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -111,6 +111,7 @@ public class SystemSettingsValidators { }); VALIDATORS.put(System.DISPLAY_COLOR_MODE_VENDOR_HINT, ANY_STRING_VALIDATOR); VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(System.SCREEN_OFF_TIMEOUT_DOCKED, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255)); VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR); VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index ce63b446efb7..6352f81b4474 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -1,3 +1,6 @@ +# Preserve line number information for debugging stack traces. +-keepattributes SourceFile,LineNumberTable + -keep class com.android.systemui.recents.OverviewProxyRecentsImpl -keep class com.android.systemui.statusbar.car.CarStatusBar -keep class com.android.systemui.statusbar.phone.StatusBar diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml index 5343411e4c95..59f87da79a11 100644 --- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml +++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml @@ -16,9 +16,8 @@ --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="@dimen/qs_security_footer_single_line_height" - android:layout_weight="1" android:gravity="center" android:clickable="true" android:visibility="gone"> diff --git a/packages/SystemUI/res/drawable/ic_account_circle.xml b/packages/SystemUI/res/drawable/ic_account_circle.xml new file mode 100644 index 000000000000..5ca99f32771b --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_account_circle.xml @@ -0,0 +1,24 @@ +<!-- + ~ Copyright (C) 2022 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M5.85,17.1Q7.125,16.125 8.7,15.562Q10.275,15 12,15Q13.725,15 15.3,15.562Q16.875,16.125 18.15,17.1Q19.025,16.075 19.513,14.775Q20,13.475 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,13.475 4.488,14.775Q4.975,16.075 5.85,17.1ZM12,13Q10.525,13 9.512,11.988Q8.5,10.975 8.5,9.5Q8.5,8.025 9.512,7.012Q10.525,6 12,6Q13.475,6 14.488,7.012Q15.5,8.025 15.5,9.5Q15.5,10.975 14.488,11.988Q13.475,13 12,13ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q13.325,20 14.5,19.613Q15.675,19.225 16.65,18.5Q15.675,17.775 14.5,17.387Q13.325,17 12,17Q10.675,17 9.5,17.387Q8.325,17.775 7.35,18.5Q8.325,19.225 9.5,19.613Q10.675,20 12,20ZM12,11Q12.65,11 13.075,10.575Q13.5,10.15 13.5,9.5Q13.5,8.85 13.075,8.425Q12.65,8 12,8Q11.35,8 10.925,8.425Q10.5,8.85 10.5,9.5Q10.5,10.15 10.925,10.575Q11.35,11 12,11ZM12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5ZM12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_account_circle_filled.xml b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml new file mode 100644 index 000000000000..47c553b52123 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_account_circle_filled.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2022 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33A7.95,7.95 0,0 1,4 12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83z"/> + <path + android:fillColor="#FFFFFF" + android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_add_supervised_user.xml b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml new file mode 100644 index 000000000000..627743ed1669 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_add_supervised_user.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2022 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. + --> +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android" > + <item android:drawable="@*android:drawable/ic_add_supervised_user" /> +</layer-list> diff --git a/packages/SystemUI/res/drawable/ic_manage_users.xml b/packages/SystemUI/res/drawable/ic_manage_users.xml new file mode 100644 index 000000000000..3a0805de1230 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_manage_users.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2022 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path android:fillColor="@android:color/white" + android:pathData="M10,12Q8.35,12 7.175,10.825Q6,9.65 6,8Q6,6.35 7.175,5.175Q8.35,4 10,4Q11.65,4 12.825,5.175Q14,6.35 14,8Q14,9.65 12.825,10.825Q11.65,12 10,12ZM2,20V17.2Q2,16.375 2.425,15.65Q2.85,14.925 3.6,14.55Q4.875,13.9 6.475,13.45Q8.075,13 10,13Q10.2,13 10.35,13Q10.5,13 10.65,13.05Q10.45,13.5 10.312,13.988Q10.175,14.475 10.1,15H10Q8.225,15 6.812,15.45Q5.4,15.9 4.5,16.35Q4.275,16.475 4.138,16.7Q4,16.925 4,17.2V18H10.3Q10.45,18.525 10.7,19.038Q10.95,19.55 11.25,20ZM16,21L15.7,19.5Q15.4,19.375 15.137,19.238Q14.875,19.1 14.6,18.9L13.15,19.35L12.15,17.65L13.3,16.65Q13.25,16.3 13.25,16Q13.25,15.7 13.3,15.35L12.15,14.35L13.15,12.65L14.6,13.1Q14.875,12.9 15.137,12.762Q15.4,12.625 15.7,12.5L16,11H18L18.3,12.5Q18.6,12.625 18.863,12.775Q19.125,12.925 19.4,13.15L20.85,12.65L21.85,14.4L20.7,15.4Q20.75,15.7 20.75,16.025Q20.75,16.35 20.7,16.65L21.85,17.65L20.85,19.35L19.4,18.9Q19.125,19.1 18.863,19.238Q18.6,19.375 18.3,19.5L18,21ZM17,18Q17.825,18 18.413,17.413Q19,16.825 19,16Q19,15.175 18.413,14.587Q17.825,14 17,14Q16.175,14 15.588,14.587Q15,15.175 15,16Q15,16.825 15.588,17.413Q16.175,18 17,18ZM10,10Q10.825,10 11.413,9.412Q12,8.825 12,8Q12,7.175 11.413,6.588Q10.825,6 10,6Q9.175,6 8.588,6.588Q8,7.175 8,8Q8,8.825 8.588,9.412Q9.175,10 10,10ZM10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8ZM10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml index 7b95cf3cfa34..1633e52e7a6c 100644 --- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml +++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml @@ -21,6 +21,7 @@ android:id="@+id/user_switcher_root" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginBottom="64dp" android:layout_marginEnd="60dp" android:layout_marginStart="60dp"> diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml index 8d02429150f0..401c4bdd55b6 100644 --- a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml +++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml @@ -29,17 +29,19 @@ <ImageView android:id="@+id/icon" + android:scaleType="centerInside" android:layout_gravity="center" android:layout_width="20dp" android:layout_height="20dp" android:contentDescription="@null" + android:tint="@color/user_switcher_fullscreen_popup_item_tint" android:layout_marginEnd="10dp" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="@*android:color/text_color_primary_device_default_dark" + android:textColor="@color/user_switcher_fullscreen_popup_item_tint" android:textSize="14sp" android:layout_gravity="start" /> </LinearLayout> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml index ae557c4e72fc..e897f75470a9 100644 --- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -20,4 +20,6 @@ <dimen name="controls_padding_horizontal">205dp</dimen> <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen> <dimen name="notification_panel_margin_bottom">56dp</dimen> + + <dimen name="keyguard_split_shade_top_margin">72dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml index 2eff692301b1..a9e6d22461b6 100644 --- a/packages/SystemUI/res/values-television/config.xml +++ b/packages/SystemUI/res/values-television/config.xml @@ -24,29 +24,6 @@ <string name="config_systemUIFactoryComponent" translatable="false"> com.android.systemui.tv.TvSystemUIFactory </string> - <!-- SystemUI Services: The classes of the stuff to start. --> - <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.util.NotificationChannels</item> - <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.privacy.television.TvOngoingPrivacyChip</item> - <item>com.android.systemui.statusbar.tv.TvStatusBar</item> - <item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item> - <item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item> - <item>com.android.systemui.statusbar.tv.VpnStatusObserver</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.usb.StorageNotification</item> - <item>com.android.systemui.power.PowerUI</item> - <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> - <item>@string/config_systemUIVendorServiceComponent</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> - <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> - <item>com.android.systemui.accessibility.WindowMagnification</item> - <item>com.android.systemui.toast.ToastUI</item> - <item>com.android.systemui.wmshell.WMShell</item> - <item>com.android.systemui.media.systemsounds.HomeSoundEffectController</item> - </string-array> <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. --> <integer name="recents_svelte_level">3</integer> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 15147786e557..faf518e73e6d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -88,6 +88,7 @@ <color name="keyguard_user_switcher_background_gradient_color">#77000000</color> <color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color> + <color name="user_switcher_fullscreen_popup_item_tint">@*android:color/text_color_primary_device_default_dark</color> <!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* --> <color name="navigation_bar_icon_color">#E5FFFFFF</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 47822b77a93f..bb1ffa8fec38 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -292,43 +292,6 @@ <!-- SystemUIFactory component --> <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string> - <!-- SystemUI Services: The classes of base stuff to start by default for all - configurations. --> - <string-array name="config_systemUIServiceComponents" translatable="false"> - <item>com.android.systemui.util.NotificationChannels</item> - <item>com.android.systemui.keyguard.KeyguardViewMediator</item> - <item>com.android.keyguard.KeyguardBiometricLockoutLogger</item> - <item>com.android.systemui.recents.Recents</item> - <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.statusbar.phone.StatusBar</item> - <item>com.android.systemui.usb.StorageNotification</item> - <item>com.android.systemui.power.PowerUI</item> - <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> - <item>@string/config_systemUIVendorServiceComponent</item> - <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> - <item>com.android.systemui.LatencyTester</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.ScreenDecorations</item> - <item>com.android.systemui.biometrics.AuthController</item> - <item>com.android.systemui.log.SessionTracker</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> - <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> - <item>com.android.systemui.theme.ThemeOverlayController</item> - <item>com.android.systemui.accessibility.WindowMagnification</item> - <item>com.android.systemui.accessibility.SystemActions</item> - <item>com.android.systemui.toast.ToastUI</item> - <item>com.android.systemui.wmshell.WMShell</item> - <item>com.android.systemui.clipboardoverlay.ClipboardListener</item> - </string-array> - - <!-- SystemUI Services: The classes of the additional stuff to start. Services here are - specified as an overlay to provide configuration-specific services that - supplement those listed in config_systemUIServiceComponents. --> - <string-array name="config_additionalSystemUIServiceComponents" translatable="false"> - </string-array> - <!-- QS tile shape store width. negative implies fill configuration instead of stroke--> <dimen name="config_qsTileStrokeWidthActive">-1dp</dimen> <dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d39e29568986..6a34adadcffe 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2402,4 +2402,8 @@ <!-- Generic "add" string [CHAR LIMIT=NONE] --> <string name="add">Add</string> + <!-- Add supervised user --> + <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string> + <!-- Manage users - For system user management [CHAR LIMIT=40] --> + <string name="manage_users">Manage users</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 5bdee2a61b9b..23ca923957b6 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -49,8 +49,11 @@ import com.android.systemui.util.NotificationChannels; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +import javax.inject.Provider; /** * Application class for SystemUI. @@ -181,17 +184,16 @@ public class SystemUIApplication extends Application implements */ public void startServicesIfNeeded() { - final String[] names = SystemUIFactory.getInstance() - .getSystemUIServiceComponents(getResources()); - final String[] additionalNames = SystemUIFactory.getInstance() - .getAdditionalSystemUIServiceComponents(getResources()); - - final ArrayList<String> serviceComponents = new ArrayList<>(); - Collections.addAll(serviceComponents, names); - Collections.addAll(serviceComponents, additionalNames); - - startServicesIfNeeded(/* metricsPrefix= */ "StartServices", - serviceComponents.toArray(new String[serviceComponents.size()])); + final String vendorComponent = SystemUIFactory.getInstance() + .getVendorComponent(getResources()); + + // Sort the startables so that we get a deterministic ordering. + // TODO: make #start idempotent and require users of CoreStartable to call it. + Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( + Comparator.comparing(Class::getName)); + sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponents()); + startServicesIfNeeded( + sortedStartables, "StartServices", vendorComponent); } /** @@ -201,16 +203,22 @@ public class SystemUIApplication extends Application implements * <p>This method must only be called from the main thread.</p> */ void startSecondaryUserServicesIfNeeded() { - String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponentsPerUser( - getResources()); - startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names); + // Sort the startables so that we get a deterministic ordering. + Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( + Comparator.comparing(Class::getName)); + sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser()); + startServicesIfNeeded( + sortedStartables, "StartSecondaryServices", null); } - private void startServicesIfNeeded(String metricsPrefix, String[] services) { + private void startServicesIfNeeded( + Map<Class<?>, Provider<CoreStartable>> startables, + String metricsPrefix, + String vendorComponent) { if (mServicesStarted) { return; } - mServices = new CoreStartable[services.length]; + mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)]; if (!mBootCompleteCache.isBootComplete()) { // check to see if maybe it was already completed long before we began @@ -230,36 +238,29 @@ public class SystemUIApplication extends Application implements TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", Trace.TRACE_TAG_APP); log.traceBegin(metricsPrefix); - final int N = services.length; - for (int i = 0; i < N; i++) { - String clsName = services[i]; - if (DEBUG) Log.d(TAG, "loading: " + clsName); - log.traceBegin(metricsPrefix + clsName); - long ti = System.currentTimeMillis(); - try { - CoreStartable obj = mComponentHelper.resolveCoreStartable(clsName); - if (obj == null) { - Constructor constructor = Class.forName(clsName).getConstructor(Context.class); - obj = (CoreStartable) constructor.newInstance(this); - } - mServices[i] = obj; - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InstantiationException - | InvocationTargetException ex) { - throw new RuntimeException(ex); - } - if (DEBUG) Log.d(TAG, "running: " + mServices[i]); - mServices[i].start(); - log.traceEnd(); + int i = 0; + for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) { + String clsName = entry.getKey().getName(); + int j = i; // Copied to make lambda happy. + timeInitialization( + clsName, + () -> mServices[j] = startStartable(clsName, entry.getValue()), + log, + metricsPrefix); + i++; + } - // Warn if initialization of component takes too long - ti = System.currentTimeMillis() - ti; - if (ti > 1000) { - Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); - } + if (vendorComponent != null) { + timeInitialization( + vendorComponent, + () -> mServices[mServices.length - 1] = + startAdditionalStartable(vendorComponent), + log, + metricsPrefix); + } + + for (i = 0; i < mServices.length; i++) { if (mBootCompleteCache.isBootComplete()) { mServices[i].onBootCompleted(); } @@ -272,6 +273,50 @@ public class SystemUIApplication extends Application implements mServicesStarted = true; } + private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log, + String metricsPrefix) { + long ti = System.currentTimeMillis(); + log.traceBegin(metricsPrefix + " " + clsName); + init.run(); + log.traceEnd(); + + // Warn if initialization of component takes too long + ti = System.currentTimeMillis() - ti; + if (ti > 1000) { + Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); + } + } + + private CoreStartable startAdditionalStartable(String clsName) { + CoreStartable startable; + if (DEBUG) Log.d(TAG, "loading: " + clsName); + try { + Constructor<?> constructor = Class.forName(clsName).getConstructor( + Context.class); + startable = (CoreStartable) constructor.newInstance(this); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InstantiationException + | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + + return startStartable(startable); + } + + private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) { + if (DEBUG) Log.d(TAG, "loading: " + clsName); + return startStartable(provider.get()); + } + + private CoreStartable startStartable(CoreStartable startable) { + if (DEBUG) Log.d(TAG, "running: " + startable); + startable.start(); + + return startable; + } + // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest... @Override public boolean addDumpable(Dumpable dumpable) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index b3be87731fbc..11fffd053143 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -32,10 +32,13 @@ import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvid import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; import com.android.wm.shell.transition.ShellTransitions; +import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import javax.inject.Provider; + /** * Class factory to provide customizable SystemUI components. */ @@ -190,24 +193,24 @@ public class SystemUIFactory { } /** - * Returns the list of system UI components that should be started. + * Returns the list of {@link CoreStartable} components that should be started at startup. */ - public String[] getSystemUIServiceComponents(Resources resources) { - return resources.getStringArray(R.array.config_systemUIServiceComponents); + public Map<Class<?>, Provider<CoreStartable>> getStartableComponents() { + return mSysUIComponent.getStartables(); } /** * Returns the list of additional system UI components that should be started. */ - public String[] getAdditionalSystemUIServiceComponents(Resources resources) { - return resources.getStringArray(R.array.config_additionalSystemUIServiceComponents); + public String getVendorComponent(Resources resources) { + return resources.getString(R.string.config_systemUIVendorServiceComponent); } /** - * Returns the list of system UI components that should be started per user. + * Returns the list of {@link CoreStartable} components that should be started per user. */ - public String[] getSystemUIServiceComponentsPerUser(Resources resources) { - return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser); + public Map<Class<?>, Provider<CoreStartable>> getStartableComponentsPerUser() { + return mSysUIComponent.getPerUserStartables(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 54664f2fdd93..72b40d42b7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -48,7 +48,7 @@ public class ClipboardListener extends CoreStartable @Override public void start() { if (DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) { + DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) { mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class)); mClipboardManager.addPrimaryClipChangedListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java index f53221c959a5..e868d43a6a8e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.app.Service; import android.content.BroadcastReceiver; -import com.android.systemui.CoreStartable; import com.android.systemui.recents.RecentsImplementation; /** @@ -37,8 +36,5 @@ public interface ContextComponentHelper { Service resolveService(String className); /** Turns a classname into an instance of the class or returns null. */ - CoreStartable resolveCoreStartable(String className); - - /** Turns a classname into an instance of the class or returns null. */ BroadcastReceiver resolveBroadcastReceiver(String className); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java index fba8d351e990..3607e2578bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.app.Service; import android.content.BroadcastReceiver; -import com.android.systemui.CoreStartable; import com.android.systemui.recents.RecentsImplementation; import java.util.Map; @@ -35,19 +34,16 @@ import javax.inject.Provider; public class ContextComponentResolver implements ContextComponentHelper { private final Map<Class<?>, Provider<Activity>> mActivityCreators; private final Map<Class<?>, Provider<Service>> mServiceCreators; - private final Map<Class<?>, Provider<CoreStartable>> mSystemUICreators; private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators; private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators; @Inject ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators, Map<Class<?>, Provider<Service>> serviceCreators, - Map<Class<?>, Provider<CoreStartable>> systemUICreators, Map<Class<?>, Provider<RecentsImplementation>> recentsCreators, Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) { mActivityCreators = activityCreators; mServiceCreators = serviceCreators; - mSystemUICreators = systemUICreators; mRecentsCreators = recentsCreators; mBroadcastReceiverCreators = broadcastReceiverCreators; } @@ -84,14 +80,6 @@ public class ContextComponentResolver implements ContextComponentHelper { return resolve(className, mServiceCreators); } - /** - * Looks up the SystemUI class name to see if Dagger has an instance of it. - */ - @Override - public CoreStartable resolveCoreStartable(String className) { - return resolve(className, mSystemUICreators); - } - private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) { try { Class<?> clazz = Class.forName(className); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index bda8e3c2ed63..73551058dfd3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -18,9 +18,11 @@ package com.android.systemui.dagger; import com.android.keyguard.clock.ClockOptionsProvider; import com.android.systemui.BootCompleteCacheImpl; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactory; +import com.android.systemui.dagger.qualifiers.PerUser; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; @@ -51,8 +53,11 @@ import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper; import com.android.wm.shell.transition.ShellTransitions; +import java.util.Map; import java.util.Optional; +import javax.inject.Provider; + import dagger.BindsInstance; import dagger.Subcomponent; @@ -65,6 +70,7 @@ import dagger.Subcomponent; DependencyProvider.class, SystemUIBinder.class, SystemUIModule.class, + SystemUICoreStartableModule.class, SystemUIDefaultModule.class}) public interface SysUIComponent { @@ -221,6 +227,16 @@ public interface SysUIComponent { Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper(); /** + * Returns {@link CoreStartable}s that should be started with the application. + */ + Map<Class<?>, Provider<CoreStartable>> getStartables(); + + /** + * Returns {@link CoreStartable}s that should be started for every user. + */ + @PerUser Map<Class<?>, Provider<CoreStartable>> getPerUserStartables(); + + /** * Member injection into the supplied argument. */ void inject(SystemUIAppComponentFactory factory); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index ec2beb15959e..b32f8786899a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -16,46 +16,11 @@ package com.android.systemui.dagger; -import com.android.keyguard.KeyguardBiometricLockoutLogger; -import com.android.systemui.CoreStartable; -import com.android.systemui.LatencyTester; -import com.android.systemui.ScreenDecorations; -import com.android.systemui.SliceBroadcastRelayHandler; -import com.android.systemui.accessibility.SystemActions; -import com.android.systemui.accessibility.WindowMagnification; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.clipboardoverlay.ClipboardListener; -import com.android.systemui.dreams.DreamOverlayRegistrant; -import com.android.systemui.dreams.SmartSpaceComplication; -import com.android.systemui.dreams.complication.DreamClockDateComplication; -import com.android.systemui.dreams.complication.DreamClockTimeComplication; -import com.android.systemui.dreams.complication.DreamWeatherComplication; -import com.android.systemui.globalactions.GlobalActionsComponent; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; -import com.android.systemui.log.SessionTracker; -import com.android.systemui.media.dream.MediaDreamSentinel; -import com.android.systemui.media.systemsounds.HomeSoundEffectController; -import com.android.systemui.power.PowerUI; -import com.android.systemui.privacy.television.TvOngoingPrivacyChip; -import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsModule; -import com.android.systemui.shortcut.ShortcutKeyDispatcher; import com.android.systemui.statusbar.dagger.StatusBarModule; -import com.android.systemui.statusbar.notification.InstantAppNotifier; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.tv.TvStatusBar; -import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel; -import com.android.systemui.theme.ThemeOverlayController; -import com.android.systemui.toast.ToastUI; -import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.volume.VolumeUI; -import com.android.systemui.wmshell.WMShell; -import dagger.Binds; import dagger.Module; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; /** * SystemUI objects that are injectable should go here. @@ -66,196 +31,4 @@ import dagger.multibindings.IntoMap; KeyguardModule.class, }) public abstract class SystemUIBinder { - /** Inject into AuthController. */ - @Binds - @IntoMap - @ClassKey(AuthController.class) - public abstract CoreStartable bindAuthController(AuthController service); - - /** Inject into SessionTracker. */ - @Binds - @IntoMap - @ClassKey(SessionTracker.class) - public abstract CoreStartable bindSessionTracker(SessionTracker service); - - /** Inject into GarbageMonitor.Service. */ - @Binds - @IntoMap - @ClassKey(GarbageMonitor.Service.class) - public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui); - - /** Inject into ClipboardListener. */ - @Binds - @IntoMap - @ClassKey(ClipboardListener.class) - public abstract CoreStartable bindClipboardListener(ClipboardListener sysui); - - /** Inject into GlobalActionsComponent. */ - @Binds - @IntoMap - @ClassKey(GlobalActionsComponent.class) - public abstract CoreStartable bindGlobalActionsComponent(GlobalActionsComponent sysui); - - /** Inject into InstantAppNotifier. */ - @Binds - @IntoMap - @ClassKey(InstantAppNotifier.class) - public abstract CoreStartable bindInstantAppNotifier(InstantAppNotifier sysui); - - /** Inject into KeyguardViewMediator. */ - @Binds - @IntoMap - @ClassKey(KeyguardViewMediator.class) - public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui); - - /** Inject into KeyguardBiometricLockoutLogger. */ - @Binds - @IntoMap - @ClassKey(KeyguardBiometricLockoutLogger.class) - public abstract CoreStartable bindKeyguardBiometricLockoutLogger( - KeyguardBiometricLockoutLogger sysui); - - /** Inject into LatencyTests. */ - @Binds - @IntoMap - @ClassKey(LatencyTester.class) - public abstract CoreStartable bindLatencyTester(LatencyTester sysui); - - /** Inject into PowerUI. */ - @Binds - @IntoMap - @ClassKey(PowerUI.class) - public abstract CoreStartable bindPowerUI(PowerUI sysui); - - /** Inject into Recents. */ - @Binds - @IntoMap - @ClassKey(Recents.class) - public abstract CoreStartable bindRecents(Recents sysui); - - /** Inject into ScreenDecorations. */ - @Binds - @IntoMap - @ClassKey(ScreenDecorations.class) - public abstract CoreStartable bindScreenDecorations(ScreenDecorations sysui); - - /** Inject into ShortcutKeyDispatcher. */ - @Binds - @IntoMap - @ClassKey(ShortcutKeyDispatcher.class) - public abstract CoreStartable bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui); - - /** Inject into SliceBroadcastRelayHandler. */ - @Binds - @IntoMap - @ClassKey(SliceBroadcastRelayHandler.class) - public abstract CoreStartable bindSliceBroadcastRelayHandler(SliceBroadcastRelayHandler sysui); - - /** Inject into StatusBar. */ - @Binds - @IntoMap - @ClassKey(StatusBar.class) - public abstract CoreStartable bindsStatusBar(StatusBar sysui); - - /** Inject into SystemActions. */ - @Binds - @IntoMap - @ClassKey(SystemActions.class) - public abstract CoreStartable bindSystemActions(SystemActions sysui); - - /** Inject into ThemeOverlayController. */ - @Binds - @IntoMap - @ClassKey(ThemeOverlayController.class) - public abstract CoreStartable bindThemeOverlayController(ThemeOverlayController sysui); - - /** Inject into ToastUI. */ - @Binds - @IntoMap - @ClassKey(ToastUI.class) - public abstract CoreStartable bindToastUI(ToastUI service); - - /** Inject into TvStatusBar. */ - @Binds - @IntoMap - @ClassKey(TvStatusBar.class) - public abstract CoreStartable bindsTvStatusBar(TvStatusBar sysui); - - /** Inject into TvNotificationPanel. */ - @Binds - @IntoMap - @ClassKey(TvNotificationPanel.class) - public abstract CoreStartable bindsTvNotificationPanel(TvNotificationPanel sysui); - - /** Inject into TvOngoingPrivacyChip. */ - @Binds - @IntoMap - @ClassKey(TvOngoingPrivacyChip.class) - public abstract CoreStartable bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui); - - /** Inject into VolumeUI. */ - @Binds - @IntoMap - @ClassKey(VolumeUI.class) - public abstract CoreStartable bindVolumeUI(VolumeUI sysui); - - /** Inject into WindowMagnification. */ - @Binds - @IntoMap - @ClassKey(WindowMagnification.class) - public abstract CoreStartable bindWindowMagnification(WindowMagnification sysui); - - /** Inject into WMShell. */ - @Binds - @IntoMap - @ClassKey(WMShell.class) - public abstract CoreStartable bindWMShell(WMShell sysui); - - /** Inject into HomeSoundEffectController. */ - @Binds - @IntoMap - @ClassKey(HomeSoundEffectController.class) - public abstract CoreStartable bindHomeSoundEffectController(HomeSoundEffectController sysui); - - /** Inject into DreamOverlay. */ - @Binds - @IntoMap - @ClassKey(DreamOverlayRegistrant.class) - public abstract CoreStartable bindDreamOverlayRegistrant( - DreamOverlayRegistrant dreamOverlayRegistrant); - - /** Inject into SmartSpaceComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(SmartSpaceComplication.Registrant.class) - public abstract CoreStartable bindSmartSpaceComplicationRegistrant( - SmartSpaceComplication.Registrant registrant); - - /** Inject into MediaDreamSentinel. */ - @Binds - @IntoMap - @ClassKey(MediaDreamSentinel.class) - public abstract CoreStartable bindMediaDreamSentinel( - MediaDreamSentinel sentinel); - - /** Inject into DreamClockTimeComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(DreamClockTimeComplication.Registrant.class) - public abstract CoreStartable bindDreamClockTimeComplicationRegistrant( - DreamClockTimeComplication.Registrant registrant); - - /** Inject into DreamClockDateComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(DreamClockDateComplication.Registrant.class) - public abstract CoreStartable bindDreamClockDateComplicationRegistrant( - DreamClockDateComplication.Registrant registrant); - - /** Inject into DreamWeatherComplication.Registrant */ - @Binds - @IntoMap - @ClassKey(DreamWeatherComplication.Registrant.class) - public abstract CoreStartable bindDreamWeatherComplicationRegistrant( - DreamWeatherComplication.Registrant registrant); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt new file mode 100644 index 000000000000..f78929f75b04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dagger + +import com.android.keyguard.KeyguardBiometricLockoutLogger +import com.android.systemui.CoreStartable +import com.android.systemui.LatencyTester +import com.android.systemui.ScreenDecorations +import com.android.systemui.SliceBroadcastRelayHandler +import com.android.systemui.accessibility.SystemActions +import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.biometrics.AuthController +import com.android.systemui.clipboardoverlay.ClipboardListener +import com.android.systemui.dagger.qualifiers.PerUser +import com.android.systemui.globalactions.GlobalActionsComponent +import com.android.systemui.keyboard.KeyboardUI +import com.android.systemui.keyguard.KeyguardViewMediator +import com.android.systemui.log.SessionTracker +import com.android.systemui.media.RingtonePlayer +import com.android.systemui.power.PowerUI +import com.android.systemui.recents.Recents +import com.android.systemui.shortcut.ShortcutKeyDispatcher +import com.android.systemui.statusbar.notification.InstantAppNotifier +import com.android.systemui.theme.ThemeOverlayController +import com.android.systemui.toast.ToastUI +import com.android.systemui.usb.StorageNotification +import com.android.systemui.util.NotificationChannels +import com.android.systemui.util.leak.GarbageMonitor +import com.android.systemui.volume.VolumeUI +import com.android.systemui.wmshell.WMShell +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +/** + * Collection of {@link CoreStartable}s that should be run on AOSP. + */ +@Module +abstract class SystemUICoreStartableModule { + /** Inject into AuthController. */ + @Binds + @IntoMap + @ClassKey(AuthController::class) + abstract fun bindAuthController(service: AuthController): CoreStartable + + /** Inject into ClipboardListener. */ + @Binds + @IntoMap + @ClassKey(ClipboardListener::class) + abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable + + /** Inject into GarbageMonitor.Service. */ + @Binds + @IntoMap + @ClassKey(GarbageMonitor::class) + abstract fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable + + /** Inject into GlobalActionsComponent. */ + @Binds + @IntoMap + @ClassKey(GlobalActionsComponent::class) + abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable + + /** Inject into InstantAppNotifier. */ + @Binds + @IntoMap + @ClassKey(InstantAppNotifier::class) + abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable + + /** Inject into KeyboardUI. */ + @Binds + @IntoMap + @ClassKey(KeyboardUI::class) + abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable + + /** Inject into KeyguardBiometricLockoutLogger */ + @Binds + @IntoMap + @ClassKey(KeyguardBiometricLockoutLogger::class) + abstract fun bindKeyguardBiometricLockoutLogger( + sysui: KeyguardBiometricLockoutLogger + ): CoreStartable + + /** Inject into KeyguardViewMediator. */ + @Binds + @IntoMap + @ClassKey(KeyguardViewMediator::class) + abstract fun bindKeyguardViewMediator(sysui: KeyguardViewMediator): CoreStartable + + /** Inject into LatencyTests. */ + @Binds + @IntoMap + @ClassKey(LatencyTester::class) + abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable + + /** Inject into NotificationChannels. */ + @Binds + @IntoMap + @ClassKey(NotificationChannels::class) + @PerUser + abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable + + /** Inject into PowerUI. */ + @Binds + @IntoMap + @ClassKey(PowerUI::class) + abstract fun bindPowerUI(sysui: PowerUI): CoreStartable + + /** Inject into Recents. */ + @Binds + @IntoMap + @ClassKey(Recents::class) + abstract fun bindRecents(sysui: Recents): CoreStartable + + /** Inject into RingtonePlayer. */ + @Binds + @IntoMap + @ClassKey(RingtonePlayer::class) + abstract fun bind(sysui: RingtonePlayer): CoreStartable + + /** Inject into ScreenDecorations. */ + @Binds + @IntoMap + @ClassKey(ScreenDecorations::class) + abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable + + /** Inject into SessionTracker. */ + @Binds + @IntoMap + @ClassKey(SessionTracker::class) + abstract fun bindSessionTracker(service: SessionTracker): CoreStartable + + /** Inject into ShortcutKeyDispatcher. */ + @Binds + @IntoMap + @ClassKey(ShortcutKeyDispatcher::class) + abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable + + /** Inject into SliceBroadcastRelayHandler. */ + @Binds + @IntoMap + @ClassKey(SliceBroadcastRelayHandler::class) + abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable + + /** Inject into StorageNotification. */ + @Binds + @IntoMap + @ClassKey(StorageNotification::class) + abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable + + /** Inject into SystemActions. */ + @Binds + @IntoMap + @ClassKey(SystemActions::class) + abstract fun bindSystemActions(sysui: SystemActions): CoreStartable + + /** Inject into ThemeOverlayController. */ + @Binds + @IntoMap + @ClassKey(ThemeOverlayController::class) + abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable + + /** Inject into ToastUI. */ + @Binds + @IntoMap + @ClassKey(ToastUI::class) + abstract fun bindToastUI(service: ToastUI): CoreStartable + + /** Inject into VolumeUI. */ + @Binds + @IntoMap + @ClassKey(VolumeUI::class) + abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable + + /** Inject into WindowMagnification. */ + @Binds + @IntoMap + @ClassKey(WindowMagnification::class) + abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable + + /** Inject into WMShell. */ + @Binds + @IntoMap + @ClassKey(WMShell::class) + abstract fun bindWMShell(sysui: WMShell): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index a17873869691..a4da6b422bde 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.dagger.StartStatusBarModule; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -86,6 +87,7 @@ import dagger.Provides; MediaModule.class, PowerModule.class, QSModule.class, + StartStatusBarModule.class, VolumeModule.class }) public abstract class SystemUIDefaultModule { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java new file mode 100644 index 000000000000..f6d5ece74ae7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dagger.qualifiers; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface PerUser { +} diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index fa951fa09ef6..e4a7406687d2 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -19,6 +19,7 @@ package com.android.systemui.dump import android.content.Context import android.os.SystemClock import android.os.Trace +import com.android.systemui.CoreStartable import com.android.systemui.R import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH @@ -27,6 +28,7 @@ import com.android.systemui.log.LogBuffer import java.io.FileDescriptor import java.io.PrintWriter import javax.inject.Inject +import javax.inject.Provider /** * Oversees SystemUI's output during bug reports (and dumpsys in general) @@ -80,7 +82,8 @@ import javax.inject.Inject class DumpHandler @Inject constructor( private val context: Context, private val dumpManager: DumpManager, - private val logBufferEulogizer: LogBufferEulogizer + private val logBufferEulogizer: LogBufferEulogizer, + private val startables: MutableMap<Class<*>, Provider<CoreStartable>> ) { /** * Dump the diagnostics! Behavior can be controlled via [args]. @@ -173,12 +176,21 @@ class DumpHandler @Inject constructor( pw.println("SystemUiServiceComponents configuration:") pw.print("vendor component: ") pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) - dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents) + val services: MutableList<String> = startables.keys + .map({ cls: Class<*> -> cls.simpleName }) + .toMutableList() + + services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent)) + dumpServiceList(pw, "global", services.toTypedArray()) dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser) } private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) { - val services: Array<String>? = context.resources.getStringArray(resId) + val services: Array<String> = context.resources.getStringArray(resId) + dumpServiceList(pw, type, services) + } + + private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) { pw.print(type) pw.print(": ") if (services == null) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 552f18894408..51101da89748 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -139,7 +139,7 @@ public class Flags { /***************************************/ // 900 - media - public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false); + public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true); public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true); public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false); diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 1c0b104b6945..6f7e73fd5190 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -52,6 +52,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -60,6 +61,10 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import javax.inject.Inject; + +/** */ +@SysUISingleton public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener { private static final String TAG = "KeyboardUI"; private static final boolean DEBUG = false; @@ -117,6 +122,7 @@ public class KeyboardUI extends CoreStartable implements InputManager.OnTabletMo private int mState; + @Inject public KeyboardUI(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index ae5f9b63fb3d..4e35d16457e8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -38,16 +38,20 @@ import android.provider.MediaStore; import android.util.Log; import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.SysUISingleton; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; +import javax.inject.Inject; + /** * Service that offers to play ringtones by {@link Uri}, since our process has * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. */ +@SysUISingleton public class RingtonePlayer extends CoreStartable { private static final String TAG = "RingtonePlayer"; private static final boolean LOGD = false; @@ -59,6 +63,7 @@ public class RingtonePlayer extends CoreStartable { private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG); private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>(); + @Inject public RingtonePlayer(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index adae07b58e7c..2ed2f4f3dbee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -47,7 +47,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - title = "Media Tap-To-Transfer Chip View" + title = WINDOW_TITLE format = PixelFormat.TRANSLUCENT setTrustedOverlay() } @@ -106,3 +106,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( } } } + +// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and +// UpdateMediaTapToTransferReceiverDisplayTest +private const val WINDOW_TITLE = "Media Transfer Chip View" diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index be45a62eeba6..919991189676 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -263,11 +263,14 @@ public class EdgeBackGestureHandler extends CurrentUserTracker // Notify FalsingManager that an intentional gesture has occurred. // TODO(b/186519446): use a different method than isFalseTouch mFalsingManager.isFalseTouch(BACK_GESTURE); - boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); - boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); - if (DEBUG_MISSING_GESTURE) { - Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown - + ", up=" + sendUp); + // Only inject back keycodes when ahead-of-time back dispatching is disabled. + if (mBackAnimation == null) { + boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + if (DEBUG_MISSING_GESTURE) { + Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + + sendDown + ", up=" + sendUp); + } } mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index c18209d9abca..4da574d571b2 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -43,6 +43,7 @@ import android.view.View; import android.view.WindowManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.window.BackEvent; import androidx.core.graphics.ColorUtils; import androidx.dynamicanimation.animation.DynamicAnimation; @@ -464,7 +465,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl @Override public void onMotionEvent(MotionEvent event) { if (mBackAnimation != null) { - mBackAnimation.onBackMotion(event); + mBackAnimation.onBackMotion( + event, mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 77feb90f575a..5df8b8082a41 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -191,6 +191,10 @@ internal class FooterActionsController @Inject constructor( reformatForNewFooter(securityFooter) val fgsFooter = fgsManagerFooterController.view securityFootersContainer?.addView(fgsFooter) + (fgsFooter.layoutParams as LinearLayout.LayoutParams).apply { + width = 0 + weight = 1f + } val visibilityListener = VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility -> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt index cc5a771f78c1..26adf464195c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt @@ -94,15 +94,28 @@ enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum { override fun getId() = _id } +/** + * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger} + * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode". + */ enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum { - @UiEvent(doc = "TODO(beverlyt)") + @UiEvent(doc = "User selected an option on the DND dialog") QS_DND_CONDITION_SELECT(420), - @UiEvent(doc = "TODO(beverlyt)") + @UiEvent(doc = "User increased countdown duration of DND from the DND dialog") QS_DND_TIME_UP(422), - @UiEvent(doc = "TODO(beverlyt)") - QS_DND_TIME_DOWN(423); + @UiEvent(doc = "User decreased countdown duration of DND from the DND dialog") + QS_DND_TIME_DOWN(423), + + @UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off") + QS_DND_DIALOG_ENABLE_FOREVER(946), + + @UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off") + QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947), + + @UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done") + QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948); override fun getId() = _id } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt index 73d6b971f785..237b66e79ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt @@ -21,6 +21,7 @@ import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon +import android.os.RemoteException import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.statusbar.IAddTileResultCallback @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.R import com.android.systemui.statusbar.CommandQueue import java.io.PrintWriter +import java.util.concurrent.atomic.AtomicBoolean import java.util.function.Consumer import javax.inject.Inject @@ -67,7 +69,11 @@ class TileServiceRequestController constructor( callback: IAddTileResultCallback ) { requestTileAdd(componentName, appName, label, icon) { - callback.onTileRequest(it) + try { + callback.onTileRequest(it) + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't respond to request", e) + } } } @@ -105,7 +111,7 @@ class TileServiceRequestController constructor( eventLogger.logTileAlreadyAdded(packageName, instanceId) return } - val dialogResponse = Consumer<Int> { response -> + val dialogResponse = SingleShotConsumer<Int> { response -> if (response == ADD_TILE) { addTile(componentName) } @@ -127,7 +133,7 @@ class TileServiceRequestController constructor( private fun createDialog( tileData: TileRequestDialog.TileData, - responseHandler: Consumer<Int> + responseHandler: SingleShotConsumer<Int> ): SystemUIDialog { val dialogClickListener = DialogInterface.OnClickListener { _, which -> if (which == Dialog.BUTTON_POSITIVE) { @@ -141,6 +147,10 @@ class TileServiceRequestController constructor( setShowForAllUsers(true) setCanceledOnTouchOutside(true) setOnCancelListener { responseHandler.accept(DISMISSED) } + // We want this in case the dialog is dismissed without it being cancelled (for example + // by going home or locking the device). We use a SingleShotConsumer so the response + // is only sent once, with the first value. + setOnDismissListener { responseHandler.accept(DISMISSED) } setPositiveButton(R.string.qs_tile_request_dialog_add, dialogClickListener) setNegativeButton(R.string.qs_tile_request_dialog_not_add, dialogClickListener) } @@ -169,6 +179,16 @@ class TileServiceRequestController constructor( } } + private class SingleShotConsumer<T>(private val consumer: Consumer<T>) : Consumer<T> { + private val dispatched = AtomicBoolean(false) + + override fun accept(t: T) { + if (dispatched.compareAndSet(false, true)) { + consumer.accept(t) + } + } + } + @SysUISingleton class Builder @Inject constructor( private val commandQueue: CommandQueue, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index a06dc8b2c19a..a33650cd3d3f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -65,6 +65,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.settings.SecureSettings; @@ -86,6 +87,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final SharedPreferences mSharedPreferences; private final SettingObserver mSettingZenDuration; private final DialogLaunchAnimator mDialogLaunchAnimator; + private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger; private boolean mListening; private boolean mShowingDetail; @@ -119,6 +121,7 @@ public class DndTile extends QSTileImpl<BooleanState> { refreshState(); } }; + mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext); } public static void setVisible(Context context, boolean visible) { @@ -211,7 +214,8 @@ public class DndTile extends QSTileImpl<BooleanState> { private Dialog makeZenModeDialog() { AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog, - true /* cancelIsNeutral */).createDialog(); + true /* cancelIsNeutral */, + mQSZenDialogMetricsLogger).createDialog(); SystemUIDialog.applyFlags(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); SystemUIDialog.registerDismissListener(dialog); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java new file mode 100644 index 000000000000..1b81a9947ee3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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.systemui.qs.tiles.dialog; + +import android.content.Context; + +import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.notification.ZenModeDialogMetricsLogger; +import com.android.systemui.qs.QSDndEvent; +import com.android.systemui.qs.QSEvents; + +/** + * Logs ui events for the DND dialog that may appear from tapping the QS DND tile. + * To see the dialog from QS: + * Settings > Notifications > Do Not Disturb > Duration for Quick Settings > Ask every time + * + * Other names for DND (Do Not Disturb) include "Zen" and "Priority only". + */ +public class QSZenModeDialogMetricsLogger extends ZenModeDialogMetricsLogger { + private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); + + public QSZenModeDialogMetricsLogger(Context context) { + super(context); + } + + @Override + public void logOnEnableZenModeForever() { + super.logOnEnableZenModeForever(); + mUiEventLogger.log(QSDndEvent.QS_DND_DIALOG_ENABLE_FOREVER); + } + + @Override + public void logOnEnableZenModeUntilAlarm() { + super.logOnEnableZenModeUntilAlarm(); + mUiEventLogger.log(QSDndEvent.QS_DND_DIALOG_ENABLE_UNTIL_ALARM); + } + + @Override + public void logOnEnableZenModeUntilCountdown() { + super.logOnEnableZenModeUntilCountdown(); + mUiEventLogger.log(QSDndEvent.QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN); + } + + @Override + public void logOnConditionSelected() { + super.logOnConditionSelected(); + mUiEventLogger.log(QSDndEvent.QS_DND_CONDITION_SELECT); + } + + @Override + public void logOnClickTimeButton(boolean up) { + super.logOnClickTimeButton(up); + mUiEventLogger.log(up + ? QSDndEvent.QS_DND_TIME_UP : QSDndEvent.QS_DND_TIME_DOWN); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index eccf27ee8841..0509a7caa719 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -73,6 +73,7 @@ import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; @@ -132,6 +133,7 @@ public class KeyguardIndicationController { private final DevicePolicyManager mDevicePolicyManager; private final UserManager mUserManager; protected final @Main DelayableExecutor mExecutor; + protected final @Background DelayableExecutor mBackgroundExecutor; private final LockPatternUtils mLockPatternUtils; private final IActivityManager mIActivityManager; private final FalsingManager mFalsingManager; @@ -203,6 +205,7 @@ public class KeyguardIndicationController { IBatteryStats iBatteryStats, UserManager userManager, @Main DelayableExecutor executor, + @Background DelayableExecutor bgExecutor, FalsingManager falsingManager, LockPatternUtils lockPatternUtils, ScreenLifecycle screenLifecycle, @@ -220,6 +223,7 @@ public class KeyguardIndicationController { mBatteryInfo = iBatteryStats; mUserManager = userManager; mExecutor = executor; + mBackgroundExecutor = bgExecutor; mLockPatternUtils = lockPatternUtils; mIActivityManager = iActivityManager; mFalsingManager = falsingManager; @@ -328,15 +332,22 @@ public class KeyguardIndicationController { private void updateDisclosure() { if (mOrganizationOwnedDevice) { - final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); - final CharSequence disclosure = getDisclosureText(organizationName); - mRotateTextViewController.updateIndication( - INDICATION_TYPE_DISCLOSURE, - new KeyguardIndication.Builder() - .setMessage(disclosure) - .setTextColor(mInitialTextColorState) - .build(), - /* updateImmediately */ false); + mBackgroundExecutor.execute(() -> { + final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); + final CharSequence disclosure = getDisclosureText(organizationName); + + mExecutor.execute(() -> { + if (mKeyguardStateController.isShowing()) { + mRotateTextViewController.updateIndication( + INDICATION_TYPE_DISCLOSURE, + new KeyguardIndication.Builder() + .setMessage(disclosure) + .setTextColor(mInitialTextColorState) + .build(), + /* updateImmediately */ false); + } + }); + }); } else { mRotateTextViewController.hideIndication(INDICATION_TYPE_DISCLOSURE); } @@ -364,26 +375,35 @@ public class KeyguardIndicationController { } private void updateOwnerInfo() { - String info = mLockPatternUtils.getDeviceOwnerInfo(); - if (info == null) { - // Use the current user owner information if enabled. - final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( - KeyguardUpdateMonitor.getCurrentUser()); - if (ownerInfoEnabled) { - info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); + // Check device owner info on a bg thread. + // It makes multiple IPCs that could block the thread it's run on. + mBackgroundExecutor.execute(() -> { + String info = mLockPatternUtils.getDeviceOwnerInfo(); + if (info == null) { + // Use the current user owner information if enabled. + final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( + KeyguardUpdateMonitor.getCurrentUser()); + if (ownerInfoEnabled) { + info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); + } } - } - if (!TextUtils.isEmpty(info)) { - mRotateTextViewController.updateIndication( - INDICATION_TYPE_OWNER_INFO, - new KeyguardIndication.Builder() - .setMessage(info) - .setTextColor(mInitialTextColorState) - .build(), - false); - } else { - mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO); - } + + // Update the UI on the main thread. + final String finalInfo = info; + mExecutor.execute(() -> { + if (!TextUtils.isEmpty(finalInfo) && mKeyguardStateController.isShowing()) { + mRotateTextViewController.updateIndication( + INDICATION_TYPE_OWNER_INFO, + new KeyguardIndication.Builder() + .setMessage(finalInfo) + .setTextColor(mInitialTextColorState) + .build(), + false); + } else { + mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO); + } + }); + }); } private void updateBattery(boolean animate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index ee12cc5679fe..7811401f1d61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -336,6 +336,9 @@ public class StatusBarStateControllerImpl implements } private void setDozeAmountInternal(float dozeAmount) { + if (Float.compare(dozeAmount, mDozeAmount) == 0) { + return; + } mDozeAmount = dozeAmount; float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount); synchronized (mListeners) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt new file mode 100644 index 000000000000..46c1abb859b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartStatusBarModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.systemui.statusbar.dagger + +import com.android.systemui.CoreStartable +import com.android.systemui.statusbar.phone.StatusBar +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface StartStatusBarModule { + /** Start the StatusBar */ + @Binds + @IntoMap + @ClassKey(StatusBar::class) + abstract fun bindsStatusBar(statusBar: StatusBar): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index e739b9f056f0..e3ebef99f45f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -204,7 +204,7 @@ public interface NotificationsModule { static VisualStabilityManager provideVisualStabilityManager( NotificationEntryManager notificationEntryManager, VisualStabilityProvider visualStabilityProvider, - Handler handler, + @Main Handler handler, StatusBarStateController statusBarStateController, WakefulnessLifecycle wakefulnessLifecycle, DumpManager dumpManager) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e1116f84c15d..d1c63e3ade70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -290,11 +290,11 @@ public class NotificationStackScrollLayoutController { @Override public void onThemeChanged() { - updateShowEmptyShadeView(); mView.updateCornerRadius(); mView.updateBgColor(); mView.updateDecorViews(); mView.reinflateViews(); + updateShowEmptyShadeView(); updateFooter(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index e24cd3e9b016..3a3f5811143f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -376,6 +376,11 @@ public class StackScrollAlgorithm { final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding; final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding; + if (stackEndHeight == 0f) { + // This should not happen, since even when the shade is empty we show EmptyShadeView + // but check just in case, so we don't return infinity or NaN. + return 0f; + } return stackHeight / stackEndHeight; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 565b2d333d4c..95a2a6e75e7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -41,6 +41,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.DejankUtils; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -115,7 +116,7 @@ public class KeyguardBouncer { BouncerExpansionCallback expansionCallback, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController keyguardBypassController, Handler handler, + KeyguardBypassController keyguardBypassController, @Main Handler handler, KeyguardSecurityModel keyguardSecurityModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { mContext = context; @@ -647,7 +648,7 @@ public class KeyguardBouncer { DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - KeyguardBypassController keyguardBypassController, Handler handler, + KeyguardBypassController keyguardBypassController, @Main Handler handler, KeyguardSecurityModel keyguardSecurityModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { mContext = context; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 3ece240bc576..7a7af4dcc935 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -46,6 +46,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyCallback; +import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -159,6 +160,7 @@ public class UserSwitcherController implements Dumpable { private final AtomicBoolean mGuestCreationScheduled; private FalsingManager mFalsingManager; private View mView; + private String mCreateSupervisedUserPackage; @Inject public UserSwitcherController(Context context, @@ -255,6 +257,9 @@ public class UserSwitcherController implements Dumpable { keyguardStateController.addCallback(mCallback); listenForCallState(); + mCreateSupervisedUserPackage = mContext.getString( + com.android.internal.R.string.config_supervisedUserCreationPackage); + dumpManager.registerDumpable(getClass().getSimpleName(), this); refreshUsers(UserHandle.USER_NULL); @@ -307,14 +312,10 @@ public class UserSwitcherController implements Dumpable { // User 0 boolean canSwitchUsers = mUserManager.getUserSwitchability( UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK; - UserInfo currentUserInfo = null; UserRecord guestRecord = null; for (UserInfo info : infos) { boolean isCurrent = currentId == info.id; - if (isCurrent) { - currentUserInfo = info; - } boolean switchToEnabled = canSwitchUsers || isCurrent; if (info.isEnabled()) { if (info.isGuest()) { @@ -322,7 +323,8 @@ public class UserSwitcherController implements Dumpable { // the icon shouldn't be enabled even if the user is current guestRecord = new UserRecord(info, null /* picture */, true /* isGuest */, isCurrent, false /* isAddUser */, - false /* isRestricted */, canSwitchUsers); + false /* isRestricted */, canSwitchUsers, + false /* isAddSupervisedUser */); } else if (info.supportsSwitchToByUser()) { Bitmap picture = bitmaps.get(info.id); if (picture == null) { @@ -337,7 +339,7 @@ public class UserSwitcherController implements Dumpable { } records.add(new UserRecord(info, picture, false /* isGuest */, isCurrent, false /* isAddUser */, false /* isRestricted */, - switchToEnabled)); + switchToEnabled, false /* isAddSupervisedUser */)); } } } @@ -345,19 +347,6 @@ public class UserSwitcherController implements Dumpable { Prefs.putBoolean(mContext, Key.SEEN_MULTI_USER, true); } - boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction( - UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); - boolean currentUserCanCreateUsers = currentUserInfo != null - && (currentUserInfo.isAdmin() - || currentUserInfo.id == UserHandle.USER_SYSTEM) - && systemCanCreateUsers; - boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked; - boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers) - && guestRecord == null; - boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers) - && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); - boolean createIsRestricted = !addUsersWhenLocked; - if (guestRecord == null) { if (mGuestUserAutoCreated) { // If mGuestIsResetting=true, the switch should be disabled since @@ -368,13 +357,14 @@ public class UserSwitcherController implements Dumpable { guestRecord = new UserRecord(null /* info */, null /* picture */, true /* isGuest */, false /* isCurrent */, false /* isAddUser */, false /* isRestricted */, - isSwitchToGuestEnabled); + isSwitchToGuestEnabled, false /* isAddSupervisedUser */); checkIfAddUserDisallowedByAdminOnly(guestRecord); records.add(guestRecord); - } else if (canCreateGuest) { + } else if (canCreateGuest(guestRecord != null)) { guestRecord = new UserRecord(null /* info */, null /* picture */, true /* isGuest */, false /* isCurrent */, - false /* isAddUser */, createIsRestricted, canSwitchUsers); + false /* isAddUser */, createIsRestricted(), canSwitchUsers, + false /* isAddSupervisedUser */); checkIfAddUserDisallowedByAdminOnly(guestRecord); records.add(guestRecord); } @@ -382,10 +372,19 @@ public class UserSwitcherController implements Dumpable { records.add(guestRecord); } - if (canCreateUser) { + if (canCreateUser()) { UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, false /* isGuest */, false /* isCurrent */, true /* isAddUser */, - createIsRestricted, canSwitchUsers); + createIsRestricted(), canSwitchUsers, + false /* isAddSupervisedUser */); + checkIfAddUserDisallowedByAdminOnly(addUserRecord); + records.add(addUserRecord); + } + + if (canCreateSupervisedUser()) { + UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */, + false /* isGuest */, false /* isCurrent */, false /* isAddUser */, + createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */); checkIfAddUserDisallowedByAdminOnly(addUserRecord); records.add(addUserRecord); } @@ -403,6 +402,40 @@ public class UserSwitcherController implements Dumpable { }.execute((SparseArray) bitmaps); } + boolean systemCanCreateUsers() { + return !mUserManager.hasBaseUserRestriction( + UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); + } + + boolean currentUserCanCreateUsers() { + UserInfo currentUser = mUserTracker.getUserInfo(); + return currentUser != null + && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM) + && systemCanCreateUsers(); + } + + boolean anyoneCanCreateUsers() { + return systemCanCreateUsers() && mAddUsersFromLockScreen; + } + + boolean canCreateGuest(boolean hasExistingGuest) { + return (currentUserCanCreateUsers() || anyoneCanCreateUsers()) + && !hasExistingGuest; + } + + boolean canCreateUser() { + return (currentUserCanCreateUsers() || anyoneCanCreateUsers()) + && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); + } + + boolean createIsRestricted() { + return !mAddUsersFromLockScreen; + } + + boolean canCreateSupervisedUser() { + return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser(); + } + private void pauseRefreshUsers() { if (!mPauseRefreshUsers) { mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS); @@ -485,6 +518,9 @@ public class UserSwitcherController implements Dumpable { } else if (record.isAddUser) { showAddUserDialog(dialogShower); return; + } else if (record.isAddSupervisedUser) { + startSupervisedUserActivity(); + return; } else { id = record.info.id; } @@ -561,6 +597,22 @@ public class UserSwitcherController implements Dumpable { } } + private void startSupervisedUserActivity() { + final Intent intent = new Intent() + .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) + .setPackage(mCreateSupervisedUserPackage) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // TODO(b/209659998): [to-be-removed] fallback activity for supervised user creation. + if (mContext.getPackageManager().resolveActivity(intent, 0) == null) { + intent.setPackage(null) + .setClassName("com.android.settings", + "com.android.settings.users.AddSupervisedUserActivity"); + } + + mContext.startActivity(intent); + } + private void listenForCallState() { mTelephonyListenerManager.addCallStateListener(mPhoneStateListener); } @@ -941,6 +993,8 @@ public class UserSwitcherController implements Dumpable { } } else if (item.isAddUser) { return context.getString(R.string.user_add_user); + } else if (item.isAddSupervisedUser) { + return context.getString(R.string.add_user_supervised); } else { return item.info.name; } @@ -955,9 +1009,11 @@ public class UserSwitcherController implements Dumpable { protected static Drawable getIconDrawable(Context context, UserRecord item) { int iconRes; if (item.isAddUser) { - iconRes = R.drawable.ic_add_circle; + iconRes = R.drawable.ic_account_circle; } else if (item.isGuest) { - iconRes = R.drawable.ic_avatar_guest_user; + iconRes = R.drawable.ic_account_circle_filled; + } else if (item.isAddSupervisedUser) { + iconRes = R.drawable.ic_add_supervised_user; } else { iconRes = R.drawable.ic_avatar_user; } @@ -1000,6 +1056,7 @@ public class UserSwitcherController implements Dumpable { public final boolean isGuest; public final boolean isCurrent; public final boolean isAddUser; + public final boolean isAddSupervisedUser; /** If true, the record is only visible to the owner and only when unlocked. */ public final boolean isRestricted; public boolean isDisabledByAdmin; @@ -1007,7 +1064,8 @@ public class UserSwitcherController implements Dumpable { public boolean isSwitchToEnabled; public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent, - boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) { + boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled, + boolean isAddSupervisedUser) { this.info = info; this.picture = picture; this.isGuest = isGuest; @@ -1015,11 +1073,12 @@ public class UserSwitcherController implements Dumpable { this.isAddUser = isAddUser; this.isRestricted = isRestricted; this.isSwitchToEnabled = isSwitchToEnabled; + this.isAddSupervisedUser = isAddSupervisedUser; } public UserRecord copyWithIsCurrent(boolean _isCurrent) { return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted, - isSwitchToEnabled); + isSwitchToEnabled, isAddSupervisedUser); } public int resolveId() { @@ -1043,6 +1102,7 @@ public class UserSwitcherController implements Dumpable { } if (isGuest) sb.append(" <isGuest>"); if (isAddUser) sb.append(" <isAddUser>"); + if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>"); if (isCurrent) sb.append(" <isCurrent>"); if (picture != null) sb.append(" <hasPicture>"); if (isRestricted) sb.append(" <isRestricted>"); diff --git a/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt new file mode 100644 index 000000000000..ad8dc825dbcb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.tv + +import com.android.systemui.CoreStartable +import com.android.systemui.SliceBroadcastRelayHandler +import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.dagger.qualifiers.PerUser +import com.android.systemui.globalactions.GlobalActionsComponent +import com.android.systemui.keyboard.KeyboardUI +import com.android.systemui.media.RingtonePlayer +import com.android.systemui.media.systemsounds.HomeSoundEffectController +import com.android.systemui.power.PowerUI +import com.android.systemui.privacy.television.TvOngoingPrivacyChip +import com.android.systemui.shortcut.ShortcutKeyDispatcher +import com.android.systemui.statusbar.notification.InstantAppNotifier +import com.android.systemui.statusbar.tv.TvStatusBar +import com.android.systemui.statusbar.tv.VpnStatusObserver +import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler +import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel +import com.android.systemui.toast.ToastUI +import com.android.systemui.usb.StorageNotification +import com.android.systemui.util.NotificationChannels +import com.android.systemui.volume.VolumeUI +import com.android.systemui.wmshell.WMShell +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +/** + * Collection of {@link CoreStartable}s that should be run on TV. + */ +@Module +abstract class TVSystemUICoreStartableModule { + /** Inject into GlobalActionsComponent. */ + @Binds + @IntoMap + @ClassKey(GlobalActionsComponent::class) + abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable + + /** Inject into HomeSoundEffectController. */ + @Binds + @IntoMap + @ClassKey(HomeSoundEffectController::class) + abstract fun bindHomeSoundEffectController(sysui: HomeSoundEffectController): CoreStartable + + /** Inject into InstantAppNotifier. */ + @Binds + @IntoMap + @ClassKey(InstantAppNotifier::class) + abstract fun bindInstantAppNotifier(sysui: InstantAppNotifier): CoreStartable + + /** Inject into KeyboardUI. */ + @Binds + @IntoMap + @ClassKey(KeyboardUI::class) + abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable + + /** Inject into NotificationChannels. */ + @Binds + @IntoMap + @ClassKey(NotificationChannels::class) + @PerUser + abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable + + /** Inject into PowerUI. */ + @Binds + @IntoMap + @ClassKey(PowerUI::class) + abstract fun bindPowerUI(sysui: PowerUI): CoreStartable + + /** Inject into RingtonePlayer. */ + @Binds + @IntoMap + @ClassKey(RingtonePlayer::class) + abstract fun bind(sysui: RingtonePlayer): CoreStartable + + /** Inject into ShortcutKeyDispatcher. */ + @Binds + @IntoMap + @ClassKey(ShortcutKeyDispatcher::class) + abstract fun bindShortcutKeyDispatcher(sysui: ShortcutKeyDispatcher): CoreStartable + + /** Inject into SliceBroadcastRelayHandler. */ + @Binds + @IntoMap + @ClassKey(SliceBroadcastRelayHandler::class) + abstract fun bindSliceBroadcastRelayHandler(sysui: SliceBroadcastRelayHandler): CoreStartable + + /** Inject into StorageNotification. */ + @Binds + @IntoMap + @ClassKey(StorageNotification::class) + abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable + + /** Inject into ToastUI. */ + @Binds + @IntoMap + @ClassKey(ToastUI::class) + abstract fun bindToastUI(service: ToastUI): CoreStartable + + /** Inject into TvNotificationHandler. */ + @Binds + @IntoMap + @ClassKey(TvNotificationHandler::class) + abstract fun bindTvNotificationHandler(sysui: TvNotificationHandler): CoreStartable + + /** Inject into TvNotificationPanel. */ + @Binds + @IntoMap + @ClassKey(TvNotificationPanel::class) + abstract fun bindTvNotificationPanel(sysui: TvNotificationPanel): CoreStartable + + /** Inject into TvOngoingPrivacyChip. */ + @Binds + @IntoMap + @ClassKey(TvOngoingPrivacyChip::class) + abstract fun bindTvOngoingPrivacyChip(sysui: TvOngoingPrivacyChip): CoreStartable + + /** Inject into TvStatusBar. */ + @Binds + @IntoMap + @ClassKey(TvStatusBar::class) + abstract fun bindTvStatusBar(sysui: TvStatusBar): CoreStartable + + /** Inject into VolumeUI. */ + @Binds + @IntoMap + @ClassKey(VolumeUI::class) + abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable + + /** Inject into VpnStatusObserver. */ + @Binds + @IntoMap + @ClassKey(VpnStatusObserver::class) + abstract fun bindVpnStatusObserver(sysui: VpnStatusObserver): CoreStartable + + /** Inject into WindowMagnification. */ + @Binds + @IntoMap + @ClassKey(WindowMagnification::class) + abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable + + /** Inject into WMShell. */ + @Binds + @IntoMap + @ClassKey(WMShell::class) + abstract fun bindWMShell(sysui: WMShell): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java index bef05ebb724e..6fdce1ae4ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java @@ -34,6 +34,7 @@ import dagger.Subcomponent; DependencyProvider.class, SystemUIBinder.class, SystemUIModule.class, + TVSystemUICoreStartableModule.class, TvSystemUIModule.class, TvSystemUIBinder.class}) public interface TvSysUIComponent extends SysUIComponent { diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java index d0fb91c9342a..23f37ec8dc69 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java @@ -16,28 +16,13 @@ package com.android.systemui.tv; -import com.android.systemui.CoreStartable; import com.android.systemui.dagger.GlobalRootComponent; -import com.android.systemui.statusbar.tv.VpnStatusObserver; -import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; import dagger.Binds; import dagger.Module; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; @Module interface TvSystemUIBinder { @Binds GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent); - - @Binds - @IntoMap - @ClassKey(TvNotificationHandler.class) - CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui); - - @Binds - @IntoMap - @ClassKey(VpnStatusObserver.class) - CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index cf361ec304e5..345fc99f8a54 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -46,10 +46,15 @@ import com.android.internal.R; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.CoreStartable; import com.android.systemui.SystemUIApplication; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.util.NotificationChannels; import java.util.List; +import javax.inject.Inject; + +/** */ +@SysUISingleton public class StorageNotification extends CoreStartable { private static final String TAG = "StorageNotification"; @@ -61,6 +66,7 @@ public class StorageNotification extends CoreStartable { private NotificationManager mNotificationManager; private StorageManager mStorageManager; + @Inject public StorageNotification(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt index d6a8ab270b84..41da44acfbf0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt @@ -27,11 +27,10 @@ import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.os.UserManager +import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowInsets -import android.view.WindowInsets.Type import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.ImageView @@ -71,8 +70,18 @@ class UserSwitcherActivity @Inject constructor( private lateinit var broadcastReceiver: BroadcastReceiver private var popupMenu: UserSwitcherPopupMenu? = null private lateinit var addButton: View - private var addUserItem: UserRecord? = null - private var addGuestItem: UserRecord? = null + private var addUserRecords = mutableListOf<UserRecord>() + // When the add users options become available, insert another option to manage users + private val manageUserRecord = UserRecord( + null /* info */, + null /* picture */, + false /* isGuest */, + false /* isCurrent */, + false /* isAddUser */, + false /* isRestricted */, + false /* isSwitchToEnabled */, + false /* isAddSupervisedUser */ + ) private val adapter = object : BaseUserAdapter(userSwitcherController) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { @@ -104,7 +113,18 @@ class UserSwitcherActivity @Inject constructor( return view } + override fun getName(context: Context, item: UserRecord): String { + return if (item == manageUserRecord) { + getString(R.string.manage_users) + } else { + super.getName(context, item) + } + } + fun findUserIcon(item: UserRecord): Drawable { + if (item == manageUserRecord) { + return getDrawable(R.drawable.ic_manage_users) + } if (item.info == null) { return getIconDrawable(this@UserSwitcherActivity, item) } @@ -169,20 +189,11 @@ class UserSwitcherActivity @Inject constructor( super.onCreate(savedInstanceState) setContentView(R.layout.user_switcher_fullscreen) + window.decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) - parent = requireViewById<ViewGroup>(R.id.user_switcher_root).apply { - setOnApplyWindowInsetsListener { - v: View, insets: WindowInsets -> - v.apply { - val l = getPaddingLeft() - val t = getPaddingTop() - val r = getPaddingRight() - setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom) - } - - WindowInsets.CONSUMED - } - } + parent = requireViewById<ViewGroup>(R.id.user_switcher_root) requireViewById<View>(R.id.cancel).apply { setOnClickListener { @@ -203,15 +214,19 @@ class UserSwitcherActivity @Inject constructor( private fun showPopupMenu() { val items = mutableListOf<UserRecord>() - addUserItem?.let { items.add(it) } - addGuestItem?.let { items.add(it) } + addUserRecords.forEach { items.add(it) } var popupMenuAdapter = ItemAdapter( this, R.layout.user_switcher_fullscreen_popup_item, layoutInflater, { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) }, - { item: UserRecord -> adapter.findUserIcon(item) } + { item: UserRecord -> adapter.findUserIcon(item).mutate().apply { + setTint(resources.getColor( + R.color.user_switcher_fullscreen_popup_item_tint, + getTheme() + )) + } } ) popupMenuAdapter.addAll(items) @@ -225,10 +240,17 @@ class UserSwitcherActivity @Inject constructor( } // -1 for the header val item = popupMenuAdapter.getItem(pos - 1) - adapter.onUserListItemClicked(item) + if (item == manageUserRecord) { + val i = Intent().setAction(Settings.ACTION_USER_SETTINGS) + this@UserSwitcherActivity.startActivity(i) + } else { + adapter.onUserListItemClicked(item) + } dismiss() popupMenu = null + + this@UserSwitcherActivity.finish() } show() @@ -245,14 +267,15 @@ class UserSwitcherActivity @Inject constructor( } } parent.removeViews(start, count) + addUserRecords.clear() val flow = requireViewById<Flow>(R.id.flow) for (i in 0 until adapter.getCount()) { val item = adapter.getItem(i) - if (item.isAddUser) { - addUserItem = item - } else if (item.isGuest && item.info == null) { - addGuestItem = item + if (item.isAddUser || + item.isAddSupervisedUser || + item.isGuest && item.info == null) { + addUserRecords.add(item) } else { val userView = adapter.getView(i, null, parent) userView.setId(View.generateViewId()) @@ -273,7 +296,8 @@ class UserSwitcherActivity @Inject constructor( } } - if (addUserItem != null || addGuestItem != null) { + if (!addUserRecords.isEmpty()) { + addUserRecords.add(manageUserRecord) addButton.visibility = View.VISIBLE } else { addButton.visibility = View.GONE diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt index 896354737e46..754a9342bfb0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt @@ -42,7 +42,7 @@ class UserSwitcherPopupMenu( setBackgroundDrawable( res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme()) ) - setModal(true) + setModal(false) setOverlapAnchor(true) } diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index ce7e4cf82081..76dfcb182e97 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -29,6 +29,9 @@ import com.android.wm.shell.pip.tv.TvPipNotificationController; import java.util.Arrays; +import javax.inject.Inject; + +// NOT Singleton. Started per-user. public class NotificationChannels extends CoreStartable { public static String ALERTS = "ALR"; public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; @@ -38,6 +41,7 @@ public class NotificationChannels extends CoreStartable { public static String TVPIP = TvPipNotificationController.NOTIFICATION_CHANNEL; // "TVPIP" public static String HINTS = "HNT"; + @Inject public NotificationChannels(Context context) { super(context); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 0cd4fb9578ff..7bb987ca7cf0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -56,12 +56,9 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Prefs; import com.android.systemui.R; -import com.android.systemui.qs.QSDndEvent; -import com.android.systemui.qs.QSEvents; import com.android.systemui.statusbar.policy.ZenModeController; import java.io.FileDescriptor; @@ -106,7 +103,6 @@ public class ZenModePanel extends FrameLayout { private final TransitionHelper mTransitionHelper = new TransitionHelper(); private final Uri mForeverId; private final ConfigurableTexts mConfigurableTexts; - private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger(); private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); @@ -666,7 +662,6 @@ public class ZenModePanel extends FrameLayout { tag.rb.setChecked(true); if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId); MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT); - mUiEventLogger.log(QSDndEvent.QS_DND_CONDITION_SELECT); select(tag.condition); announceConditionSelection(tag); } @@ -772,7 +767,6 @@ public class ZenModePanel extends FrameLayout { private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up); - mUiEventLogger.log(up ? QSDndEvent.QS_DND_TIME_UP : QSDndEvent.QS_DND_TIME_DOWN); Condition newCondition = null; final int N = MINUTE_BUCKETS.length; if (mBucketIndex == -1) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 24b01e079b42..6736bfd21740 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -307,7 +307,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */, 0 /* flags */); users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */, - false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */)); + false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */)); } return users; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index 9e67eda57607..57fbbc95efba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -62,7 +62,7 @@ class DumpHandlerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer) + dumpHandler = DumpHandler(mContext, dumpManager, logBufferEulogizer, mutableMapOf()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt index a1c60a648de9..bdfbca47e569 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt @@ -20,6 +20,7 @@ import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon +import android.os.RemoteException import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId @@ -275,11 +276,89 @@ class TileServiceRequestControllerTest : SysuiTestCase() { assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) } + @Test + fun interfaceThrowsRemoteException_doesntCrash() { + val cancelListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) + val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) + verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) + + val callback = object : IAddTileResultCallback.Stub() { + override fun onTileRequest(p0: Int) { + throw RemoteException() + } + } + captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) + + cancelListenerCaptor.value.onCancel(tileRequestDialog) + } + + @Test + fun testDismissDialogResponse() { + val dismissListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) + + val callback = Callback() + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) + + dismissListenerCaptor.value.onDismiss(tileRequestDialog) + assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) + } + + @Test + fun addTileAndThenDismissSendsOnlyAddTile() { + // After clicking, the dialog is dismissed. This tests that only one response + // is sent (the first one) + val dismissListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) + val clickListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) + + val callback = Callback() + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) + verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) + + clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) + dismissListenerCaptor.value.onDismiss(tileRequestDialog) + + assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.ADD_TILE) + assertThat(callback.timesCalled).isEqualTo(1) + } + + @Test + fun cancelAndThenDismissSendsOnlyOnce() { + // After cancelling, the dialog is dismissed. This tests that only one response + // is sent. + val dismissListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) + val cancelListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) + + val callback = Callback() + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) + verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) + + cancelListenerCaptor.value.onCancel(tileRequestDialog) + dismissListenerCaptor.value.onDismiss(tileRequestDialog) + + assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.DISMISSED) + assertThat(callback.timesCalled).isEqualTo(1) + } + private class Callback : IAddTileResultCallback.Stub(), Consumer<Int> { var lastAccepted: Int? = null private set + + var timesCalled = 0 + private set + override fun accept(t: Int) { lastAccepted = t + timesCalled++ } override fun onTileRequest(r: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index 3a3d1546984d..9b0142d6a8fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -147,5 +147,6 @@ class UserDetailViewAdapterTest : SysuiTestCase() { current, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 98e285743706..419e84edd298 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -229,8 +229,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController = new KeyguardIndicationController(mContext, mWakeLockBuilder, mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, - mUserManager, mExecutor, mFalsingManager, mLockPatternUtils, mScreenLifecycle, - mIActivityManager, mKeyguardBypassController); + mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils, + mScreenLifecycle, mIActivityManager, mKeyguardBypassController); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); @@ -244,6 +244,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mKeyguardStateController).addCallback( mKeyguardStateControllerCallbackCaptor.capture()); mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); + + mExecutor.runAllReady(); + reset(mRotateTextViewController); } @Test @@ -328,9 +331,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_unmanaged() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyHideIndication(INDICATION_TYPE_DISCLOSURE); } @@ -338,9 +343,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_deviceOwner_noOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric); } @@ -348,11 +355,13 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_orgOwnedDeviceWithManagedProfile_noOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true); when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList( new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE))); when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(null); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric); } @@ -360,9 +369,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_deviceOwner_withOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization); } @@ -370,23 +381,27 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_orgOwnedDeviceWithManagedProfile_withOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true); when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList( new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE))); when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(ORGANIZATION_NAME); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization); } @Test public void disclosure_updateOnTheFly() { + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); createController(); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric); reset(mRotateTextViewController); @@ -394,12 +409,14 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization); reset(mRotateTextViewController); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyHideIndication(INDICATION_TYPE_DISCLOSURE); } @@ -408,11 +425,13 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void disclosure_deviceOwner_financedDeviceWithOrganizationName() { createController(); + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) .thenReturn(DEVICE_OWNER_TYPE_FINANCED); sendUpdateDisclosureBroadcast(); + mExecutor.runAllReady(); verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization); } @@ -729,11 +748,13 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void testEmptyOwnerInfoHidesIndicationArea() { createController(); - // GIVEN the owner info is set to an empty string + // GIVEN the owner info is set to an empty string & keyguard is showing + when(mKeyguardStateController.isShowing()).thenReturn(true); when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn(""); // WHEN asked to update the indication area mController.setVisible(true); + int runTasks = mExecutor.runAllReady(); // THEN the owner info should be hidden verifyHideIndication(INDICATION_TYPE_OWNER_INFO); @@ -763,6 +784,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN keyguard showing changed called mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + mExecutor.runAllReady(); // THEN persistent messages are updated (in this case, most messages are hidden since // no info is provided) - verify that this happens diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index a5ea897b6a6f..6abdea5d9230 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -23,14 +23,18 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.statusbar.StatusBarStateController import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @@ -73,4 +77,14 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[1]) assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[2]) } + + @Test + fun testSetDozeAmountInternal_onlySetsOnce() { + val listener = mock(StatusBarStateController.StateListener::class.java) + controller.addCallback(listener) + + controller.setDozeAmount(0.5f, false /* animated */) + controller.setDozeAmount(0.5f, false /* animated */) + verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt index e479882ac50a..0dd6cbb7995a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -193,5 +193,6 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { isCurrentUser, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index 9a7e702152b4..9dabf6907f7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -130,6 +131,21 @@ class UserSwitcherControllerTest : SysuiTestCase() { .thenReturn(true) `when`(notificationShadeWindowView.context).thenReturn(context) + // Since userSwitcherController involves InteractionJankMonitor. + // Let's fulfill the dependencies. + val mockedContext = mock(Context::class.java) + doReturn(mockedContext).`when`(notificationShadeWindowView).context + doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow + doNothing().`when`(threadedRenderer).addObserver(any()) + doNothing().`when`(threadedRenderer).removeObserver(any()) + doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer + + picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user)) + + setupController() + } + + private fun setupController() { userSwitcherController = UserSwitcherController( context, activityManager, @@ -153,18 +169,6 @@ class UserSwitcherControllerTest : SysuiTestCase() { dumpManager, dialogLaunchAnimator) userSwitcherController.mPauseRefreshUsers = true - - // Since userSwitcherController involves InteractionJankMonitor. - // Let's fulfill the dependencies. - val mockedContext = mock(Context::class.java) - doReturn(mockedContext).`when`(notificationShadeWindowView).context - doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow - doNothing().`when`(threadedRenderer).addObserver(any()) - doNothing().`when`(threadedRenderer).removeObserver(any()) - doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer - userSwitcherController.init(notificationShadeWindowView) - - picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user)) userSwitcherController.init(notificationShadeWindowView) } @@ -177,7 +181,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -196,7 +201,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -220,7 +226,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(ownerId) `when`(userTracker.userInfo).thenReturn(ownerInfo) @@ -240,7 +247,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -262,7 +270,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -283,7 +292,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestInfo.id) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -302,7 +312,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { true /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -323,7 +334,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -357,7 +369,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { false /* current */, false /* isAddUser */, false /* isRestricted */, - true /* isSwitchToEnabled */) + true /* isSwitchToEnabled */, + false /* isAddSupervisedUser */) `when`(userTracker.userId).thenReturn(guestId) `when`(userTracker.userInfo).thenReturn(guestInfo) @@ -389,7 +402,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { userSwitcherController.users.add(UserSwitcherController.UserRecord( UserInfo(id, name, 0), null, false, isCurrent, false, - false, false + false, false, false )) } val bgUserName = "background_user" @@ -412,4 +425,42 @@ class UserSwitcherControllerTest : SysuiTestCase() { `when`(userTracker.userId).thenReturn(1) assertEquals(false, userSwitcherController.isSystemUser) } + + @Test + fun testCanCreateSupervisedUserWithConfiguredPackage() { + // GIVEN the supervised user creation package is configured + `when`(context.getString( + com.android.internal.R.string.config_supervisedUserCreationPackage)) + .thenReturn("some_pkg") + + // AND the current user is allowed to create new users + `when`(userTracker.userId).thenReturn(ownerId) + `when`(userTracker.userInfo).thenReturn(ownerInfo) + + // WHEN the controller is started with the above config + setupController() + testableLooper.processAllMessages() + + // THEN a supervised user can be constructed + assertTrue(userSwitcherController.canCreateSupervisedUser()) + } + + @Test + fun testCannotCreateSupervisedUserWithConfiguredPackage() { + // GIVEN the supervised user creation package is NOT configured + `when`(context.getString( + com.android.internal.R.string.config_supervisedUserCreationPackage)) + .thenReturn(null) + + // AND the current user is allowed to create new users + `when`(userTracker.userId).thenReturn(ownerId) + `when`(userTracker.userInfo).thenReturn(ownerInfo) + + // WHEN the controller is started with the above config + setupController() + testableLooper.processAllMessages() + + // THEN a supervised user can NOT be constructed + assertFalse(userSwitcherController.canCreateSupervisedUser()) + } } diff --git a/services/Android.bp b/services/Android.bp index 01893039e772..e010469b2773 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -23,6 +23,7 @@ java_defaults { "-Xep:FormatString:ERROR", "-Xep:ArrayHashCode:ERROR", "-Xep:SelfAssignment:ERROR", + "-Xep:ArrayEquals:ERROR", // NOTE: only enable to generate local patchfiles // "-XepPatchChecks:refaster:frameworks/base/errorprone/refaster/EfficientXml.java.refaster", // "-XepPatchLocation:/tmp/refaster/", diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 8b62a64f57d4..7f103144b7fb 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -25,6 +25,7 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILIT import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK; import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK; @@ -66,6 +67,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Trace; import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; @@ -80,15 +82,21 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.function.pooled.PooledLambda; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.IInputSessionWithIdCallback; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.accessibility.magnification.MagnificationProcessor; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -180,6 +188,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ boolean mLastAccessibilityButtonCallbackState; + boolean mRequestImeApis; + int mFetchFlags; long mNotificationTimeout; @@ -271,6 +281,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ void onDoubleTapAndHold(int displayId); + void requestImeLocked(AccessibilityServiceConnection connection); + + void unbindImeLocked(AccessibilityServiceConnection connection); } public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, @@ -374,6 +387,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0; mRequestAccessibilityButton = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; + // TODO(b/218193835): request ime when ime flag is set and clean up when ime flag is unset + mRequestImeApis = (info.flags + & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0; } protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { @@ -1610,6 +1626,27 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available); } + public void createImeSessionLocked() { + mInvocationHandler.createImeSessionLocked(); + } + + public void setImeSessionEnabledLocked(IInputMethodSession session, boolean enabled) { + mInvocationHandler.setImeSessionEnabledLocked(session, enabled); + } + + public void bindInputLocked(InputBinding binding) { + mInvocationHandler.bindInputLocked(binding); + } + + public void unbindInputLocked() { + mInvocationHandler.unbindInputLocked(); + } + + public void startInputLocked(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + mInvocationHandler.startInputLocked(startInputToken, inputContext, editorInfo, restarting); + } + /** * Called by the invocation handler to notify the service that the * state of magnification has changed. @@ -1732,6 +1769,84 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + private void createImeSessionInternal() { + final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); + if (listener != null) { + try { + if (svcClientTracingEnabled()) { + logTraceSvcClient("createImeSession", ""); + } + AccessibilityCallback callback = new AccessibilityCallback(); + listener.createImeSession(callback); + } catch (RemoteException re) { + Slog.e(LOG_TAG, + "Error requesting IME session from " + mService, re); + } + } + } + + private void setImeSessionEnabledInternal(IInputMethodSession session, boolean enabled) { + final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); + if (listener != null && session != null) { + try { + if (svcClientTracingEnabled()) { + logTraceSvcClient("createImeSession", ""); + } + listener.setImeSessionEnabled(session, enabled); + } catch (RemoteException re) { + Slog.e(LOG_TAG, + "Error requesting IME session from " + mService, re); + } + } + } + + private void bindInputInternal(InputBinding binding) { + final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); + if (listener != null) { + try { + if (svcClientTracingEnabled()) { + logTraceSvcClient("bindInput", binding.toString()); + } + listener.bindInput(binding); + } catch (RemoteException re) { + Slog.e(LOG_TAG, + "Error binding input to " + mService, re); + } + } + } + + private void unbindInputInternal() { + final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); + if (listener != null) { + try { + if (svcClientTracingEnabled()) { + logTraceSvcClient("unbindInput", ""); + } + listener.unbindInput(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, + "Error unbinding input to " + mService, re); + } + } + } + + private void startInputInternal(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); + if (listener != null) { + try { + if (svcClientTracingEnabled()) { + logTraceSvcClient("startInput", startInputToken + " " + + inputContext + " " + editorInfo + restarting); + } + listener.startInput(startInputToken, inputContext, editorInfo, restarting); + } catch (RemoteException re) { + Slog.e(LOG_TAG, + "Error starting input to " + mService, re); + } + } + } + protected IAccessibilityServiceClient getServiceInterfaceSafely() { synchronized (mLock) { return mServiceInterface; @@ -1925,6 +2040,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7; private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8; private static final int MSG_ON_SYSTEM_ACTIONS_CHANGED = 9; + private static final int MSG_CREATE_IME_SESSION = 10; + private static final int MSG_SET_IME_SESSION_ENABLED = 11; + private static final int MSG_BIND_INPUT = 12; + private static final int MSG_UNBIND_INPUT = 13; + private static final int MSG_START_INPUT = 14; /** List of magnification callback states, mapping from displayId -> Boolean */ @GuardedBy("mlock") @@ -1974,6 +2094,29 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ notifySystemActionsChangedInternal(); break; } + case MSG_CREATE_IME_SESSION: + createImeSessionInternal(); + break; + case MSG_SET_IME_SESSION_ENABLED: + final boolean enabled = (message.arg1 != 0); + final IInputMethodSession session = (IInputMethodSession) message.obj; + setImeSessionEnabledInternal(session, enabled); + break; + case MSG_BIND_INPUT: + final InputBinding binding = (InputBinding) message.obj; + bindInputInternal(binding); + break; + case MSG_UNBIND_INPUT: + unbindInputInternal(); + break; + case MSG_START_INPUT: + final boolean restarting = (message.arg1 != 0); + final SomeArgs args = (SomeArgs) message.obj; + final IBinder startInputToken = (IBinder) args.arg1; + final IInputContext inputContext = (IInputContext) args.arg2; + final EditorInfo editorInfo = (EditorInfo) args.arg3; + startInputInternal(startInputToken, inputContext, editorInfo, restarting); + break; default: { throw new IllegalArgumentException("Unknown message: " + type); } @@ -2036,6 +2179,37 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ (available ? 1 : 0), 0); msg.sendToTarget(); } + + public void createImeSessionLocked() { + final Message msg = obtainMessage(MSG_CREATE_IME_SESSION); + msg.sendToTarget(); + } + + public void setImeSessionEnabledLocked(IInputMethodSession session, boolean enabled) { + final Message msg = obtainMessage(MSG_SET_IME_SESSION_ENABLED, (enabled ? 1 : 0), + 0, session); + msg.sendToTarget(); + } + + public void bindInputLocked(InputBinding binding) { + final Message msg = obtainMessage(MSG_BIND_INPUT, binding); + msg.sendToTarget(); + } + + public void unbindInputLocked() { + final Message msg = obtainMessage(MSG_UNBIND_INPUT); + msg.sendToTarget(); + } + + public void startInputLocked(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = startInputToken; + args.arg2 = inputContext; + args.arg3 = editorInfo; + final Message msg = obtainMessage(MSG_START_INPUT, restarting ? 1 : 0, 0, args); + msg.sendToTarget(); + } } public boolean isServiceHandlesDoubleTapEnabled() { @@ -2185,4 +2359,18 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ Binder.restoreCallingIdentity(identity); } } + + private static final class AccessibilityCallback extends IInputSessionWithIdCallback.Stub { + @Override + public void sessionCreated(IInputMethodSession session, int id) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.sessionCreated"); + final long ident = Binder.clearCallingIdentity(); + try { + InputMethodManagerInternal.get().onSessionForAccessibilityCreated(id, session); + } finally { + Binder.restoreCallingIdentity(ident); + } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + } }
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 5b580d9d829c..62da981bd9a7 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -115,6 +115,8 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.IWindowMagnificationConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; @@ -128,12 +130,16 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodSession; +import com.android.server.AccessibilityManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.accessibility.magnification.MagnificationScaleProvider; import com.android.server.accessibility.magnification.WindowMagnificationManager; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -231,7 +237,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final MainHandler mMainHandler; - // Lazily initialized - access through getSystemActionPerfomer() + // Lazily initialized - access through getSystemActionPerformer() private SystemActionPerformer mSystemActionPerformer; private InteractionBridge mInteractionBridge; @@ -273,6 +279,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; + private InputBinding mInputBinding; + IBinder mStartInputToken; + IInputContext mInputContext; + EditorInfo mEditorInfo; + boolean mRestarting; + boolean mInputSessionRequested; + private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); } @@ -298,6 +311,42 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private static final class LocalServiceImpl extends AccessibilityManagerInternal { + @NonNull + private final AccessibilityManagerService mService; + + LocalServiceImpl(@NonNull AccessibilityManagerService service) { + mService = service; + } + + @Override + public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, + boolean enabled) { + mService.setImeSessionEnabled(sessions, enabled); + } + + @Override + public void unbindInput() { + mService.unbindInput(); + } + + @Override + public void bindInput(InputBinding binding) { + mService.bindInput(binding); + } + + @Override + public void createImeSession(ArraySet<Integer> ignoreSet) { + mService.createImeSession(ignoreSet); + } + + @Override + public void startInput(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + mService.startInput(startInputToken, inputContext, editorInfo, restarting); + } + } + public static final class Lifecycle extends SystemService { private final AccessibilityManagerService mService; @@ -308,6 +357,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onStart() { + LocalServices.addService(AccessibilityManagerInternal.class, + new LocalServiceImpl(mService)); publishBinderService(Context.ACCESSIBILITY_SERVICE, mService); } @@ -3463,27 +3514,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) - public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.SET_SYSTEM_AUDIO_CAPTION, - "setSystemAudioCaptioningRequested"); + "setSystemAudioCaptioningEnabled"); - mCaptioningManagerImpl.setSystemAudioCaptioningRequested(isEnabled, userId); + mCaptioningManagerImpl.setSystemAudioCaptioningEnabled(isEnabled, userId); } @Override - public boolean isSystemAudioCaptioningUiRequested(int userId) { - return mCaptioningManagerImpl.isSystemAudioCaptioningUiRequested(userId); + public boolean isSystemAudioCaptioningUiEnabled(int userId) { + return mCaptioningManagerImpl.isSystemAudioCaptioningUiEnabled(userId); } @Override @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) - public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.SET_SYSTEM_AUDIO_CAPTION, - "setSystemAudioCaptioningUiRequested"); + "setSystemAudioCaptioningUiEnabled"); - mCaptioningManagerImpl.setSystemAudioCaptioningUiRequested(isEnabled, userId); + mCaptioningManagerImpl.setSystemAudioCaptioningUiEnabled(isEnabled, userId); } @Override @@ -4240,6 +4291,45 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub this, displayId)); } + @Override + public void requestImeLocked(AccessibilityServiceConnection connection) { + mMainHandler.sendMessage(obtainMessage( + AccessibilityManagerService::createSessionForConnection, this, connection)); + mMainHandler.sendMessage(obtainMessage( + AccessibilityManagerService::bindAndStartInputForConnection, this, connection)); + } + + @Override + public void unbindImeLocked(AccessibilityServiceConnection connection) { + mMainHandler.sendMessage(obtainMessage( + AccessibilityManagerService::unbindInputForConnection, this, connection)); + } + + private void createSessionForConnection(AccessibilityServiceConnection connection) { + synchronized (mLock) { + if (mInputSessionRequested) { + connection.createImeSessionLocked(); + } + } + } + + private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) { + synchronized (mLock) { + if (mInputBinding != null) { + connection.bindInputLocked(mInputBinding); + connection.startInputLocked(mStartInputToken, mInputContext, mEditorInfo, + mRestarting); + } + } + } + + private void unbindInputForConnection(AccessibilityServiceConnection connection) { + InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId); + synchronized (mLock) { + connection.unbindInputLocked(); + } + } + private void onDoubleTapAndHoldInternal(int displayId) { synchronized (mLock) { if (mHasInputFilter && mInputFilter != null) { @@ -4268,4 +4358,100 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public AccessibilityTraceManager getTraceManager() { return mTraceManager; } + + /** + * Bind input for accessibility services which request ime capabilities. + * + * @param binding Information given to an accessibility service about a client connecting to it. + */ + public void bindInput(InputBinding binding) { + AccessibilityUserState userState; + synchronized (mLock) { + // Keep records of these in case new Accessibility Services are enabled. + mInputBinding = binding; + userState = getCurrentUserStateLocked(); + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final AccessibilityServiceConnection service = userState.mBoundServices.get(i); + if (service.requestImeApis()) { + service.bindInputLocked(binding); + } + } + } + } + + /** + * Unbind input for accessibility services which request ime capabilities. + */ + public void unbindInput() { + AccessibilityUserState userState; + // TODO(b/218182733): Resolve the Imf lock and mLock possible deadlock + synchronized (mLock) { + userState = getCurrentUserStateLocked(); + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final AccessibilityServiceConnection service = userState.mBoundServices.get(i); + if (service.requestImeApis()) { + service.unbindInputLocked(); + } + } + } + } + + /** + * Start input for accessibility services which request ime capabilities. + */ + public void startInput(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + AccessibilityUserState userState; + synchronized (mLock) { + // Keep records of these in case new Accessibility Services are enabled. + mStartInputToken = startInputToken; + mInputContext = inputContext; + mEditorInfo = editorInfo; + mRestarting = restarting; + userState = getCurrentUserStateLocked(); + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final AccessibilityServiceConnection service = userState.mBoundServices.get(i); + if (service.requestImeApis()) { + service.startInputLocked(startInputToken, inputContext, editorInfo, restarting); + } + } + } + } + + /** + * Request input sessions from all accessibility services which request ime capabilities and + * whose id is not in the ignoreSet + */ + public void createImeSession(ArraySet<Integer> ignoreSet) { + AccessibilityUserState userState; + synchronized (mLock) { + mInputSessionRequested = true; + userState = getCurrentUserStateLocked(); + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final AccessibilityServiceConnection service = userState.mBoundServices.get(i); + if ((!ignoreSet.contains(service.mId)) && service.requestImeApis()) { + service.createImeSessionLocked(); + } + } + } + } + + /** + * Enable or disable the sessions. + * + * @param sessions Sessions to enable or disable. + * @param enabled True if enable the sessions or false if disable the sessions. + */ + public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, boolean enabled) { + AccessibilityUserState userState; + synchronized (mLock) { + userState = getCurrentUserStateLocked(); + for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) { + final AccessibilityServiceConnection service = userState.mBoundServices.get(i); + if (sessions.contains(service.mId) && service.requestImeApis()) { + service.setImeSessionEnabledLocked(sessions.get(service.mId), enabled); + } + } + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 8f7260f0df83..06310284b56c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -127,6 +127,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } public void unbindLocked() { + if (requestImeApis()) { + mSystemSupport.unbindImeLocked(this); + } mContext.unbindService(this); AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; @@ -188,6 +191,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect // the new configuration (for example, initializing the input filter). mMainHandler.sendMessage(obtainMessage( AccessibilityServiceConnection::initializeService, this)); + if (requestImeApis()) { + mSystemSupport.requestImeLocked(this); + } } } @@ -371,6 +377,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (!isConnectedLocked()) { return; } + if (requestImeApis()) { + mSystemSupport.unbindImeLocked(this); + } mAccessibilityServiceInfo.crashed = true; AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState != null) { @@ -512,6 +521,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect mMainHandler.sendMessage(msg); } + public boolean requestImeApis() { + return mRequestImeApis; + } + private void notifyMotionEventInternal(MotionEvent event) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { diff --git a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java index 39780d21486d..0fc6c8d4bab5 100644 --- a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java +++ b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java @@ -40,7 +40,7 @@ public class CaptioningManagerImpl implements CaptioningManager.SystemAudioCapti * @param userId The user Id. */ @Override - public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) { final long identity = Binder.clearCallingIdentity(); try { Settings.Secure.putIntForUser(mContext.getContentResolver(), @@ -57,7 +57,7 @@ public class CaptioningManagerImpl implements CaptioningManager.SystemAudioCapti * @return the system audio caption UI enabled state. */ @Override - public boolean isSystemAudioCaptioningUiRequested(int userId) { + public boolean isSystemAudioCaptioningUiEnabled(int userId) { final long identity = Binder.clearCallingIdentity(); try { return Settings.Secure.getIntForUser(mContext.getContentResolver(), @@ -75,7 +75,7 @@ public class CaptioningManagerImpl implements CaptioningManager.SystemAudioCapti * @param userId The user Id. */ @Override - public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) { final long identity = Binder.clearCallingIdentity(); try { Settings.Secure.putIntForUser(mContext.getContentResolver(), diff --git a/services/api/current.txt b/services/api/current.txt index dcf7e64479da..e46c97247681 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -38,8 +38,8 @@ package com.android.server { package com.android.server.am { public interface ActivityManagerLocal { + method public boolean bindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException; method public boolean canStartForegroundService(int, int, @NonNull String); - method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException; } } diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java index 0509e0cf5ccc..55246e14d592 100644 --- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java +++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java @@ -16,7 +16,9 @@ package com.android.server.companion; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; + +import static java.util.concurrent.TimeUnit.DAYS; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -37,17 +39,16 @@ import com.android.server.LocalServices; */ public class AssociationCleanUpService extends JobService { private static final int JOB_ID = AssociationCleanUpService.class.hashCode(); - private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day - private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService( - CompanionDeviceManagerServiceInternal.class); + private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1); @Override public boolean onStartJob(final JobParameters params) { - Slog.i(LOG_TAG, "Execute the Association CleanUp job"); + Slog.i(TAG, "Execute the Association CleanUp job"); // Special policy for APP_STREAMING role that need to revoke associations if the device // does not connect for 3 months. AsyncTask.execute(() -> { - mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); + LocalServices.getService(CompanionDeviceManagerServiceInternal.class) + .associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); jobFinished(params, false); }); return true; @@ -55,7 +56,7 @@ public class AssociationCleanUpService extends JobService { @Override public boolean onStopJob(final JobParameters params) { - Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId() + Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId() + ", reason=" + JobParameters.getInternalReasonCodeDescription( params.getInternalStopReasonCode())); @@ -63,7 +64,7 @@ public class AssociationCleanUpService extends JobService { } static void schedule(Context context) { - Slog.i(LOG_TAG, "Scheduling the Association Cleanup job"); + Slog.i(TAG, "Scheduling the Association Cleanup job"); final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); final JobInfo job = new JobInfo.Builder(JOB_ID, new ComponentName(context, AssociationCleanUpService.class)) diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index cef0e83f6006..eaa99f74e24e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -18,31 +18,28 @@ package com.android.server.companion; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; -import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; -import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; -import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Binder.getCallingUid; import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; -import static com.android.internal.util.CollectionUtils.find; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; -import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage; +import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; +import static com.android.server.companion.PackageUtils.getPackageInfo; import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; +import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation; import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.NonNull; @@ -53,26 +50,15 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.DeviceNotAssociatedException; import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceManager; import android.companion.IOnAssociationsChangedListener; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; @@ -93,11 +79,10 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; -import android.permission.PermissionControllerManager; import android.text.BidiFormatter; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.ExceptionUtils; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -112,86 +97,49 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.pm.UserManagerInternal; -import com.android.server.wm.ActivityTaskManagerInternal; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.TimeZone; -/** @hide */ @SuppressLint("LongLogTag") -public class CompanionDeviceManagerService extends SystemService - implements AssociationStore.OnChangeListener { - static final String LOG_TAG = "CompanionDeviceManagerService"; +public class CompanionDeviceManagerService extends SystemService { + static final String TAG = "CompanionDeviceManagerService"; static final boolean DEBUG = false; /** Range of Association IDs allocated for a user.*/ - static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; - - private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000; - private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000; - - static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000; - + private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; - private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW = - 90L * 24 * 60 * 60 * 1000; // 3 months - - private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - static { - sDateFormat.setTimeZone(TimeZone.getDefault()); - } + private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW = DAYS.toMillis(3 * 30); // 3 months - // Persistent data store for all Associations. private PersistentDataStore mPersistentStore; - private final AssociationStoreImpl mAssociationStore = new AssociationStoreImpl(); - private AssociationRequestsProcessor mAssociationRequestsProcessor; + private final PersistUserStateHandler mUserPersistenceHandler; - private PowerWhitelistManager mPowerWhitelistManager; - private IAppOpsService mAppOpsManager; - private BluetoothAdapter mBluetoothAdapter; - private UserManager mUserManager; - - private ScanCallback mBleScanCallback = new BleScanCallback(); - PermissionControllerManager mPermissionControllerManager; - - private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener = - new BluetoothDeviceConnectedListener(); - private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver(); - private List<String> mCurrentlyConnectedDevices = new ArrayList<>(); - Set<Integer> mPresentSelfManagedDevices = new HashSet<>(); - private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>(); - private UnbindDeviceListenersRunnable - mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable(); - private ArrayMap<String, TriggerDeviceDisappearedRunnable> mTriggerDeviceDisappearedRunnables = - new ArrayMap<>(); - private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = - new RemoteCallbackList<>(); - private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this); + private final AssociationStoreImpl mAssociationStore; + private AssociationRequestsProcessor mAssociationRequestsProcessor; + private CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private CompanionApplicationController mCompanionAppController; - final Handler mMainHandler = Handler.getMain(); - private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler(); - private CompanionDevicePresenceController mCompanionDevicePresenceController; + private final ActivityManagerInternal mAmInternal; + private final IAppOpsService mAppOpsManager; + private final PowerWhitelistManager mPowerWhitelistManager; + private final UserManager mUserManager; + final PackageManagerInternal mPackageManagerInternal; /** - * A structure that consist of two nested maps, and effectively maps (userId + packageName) to + * A structure that consists of two nested maps, and effectively maps (userId + packageName) to * a list of IDs that have been previously assigned to associations for that package. * We maintain this structure so that we never re-use association IDs for the same package * (until it's uninstalled). @@ -199,9 +147,8 @@ public class CompanionDeviceManagerService extends SystemService @GuardedBy("mPreviouslyUsedIds") private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); - ActivityTaskManagerInternal mAtmInternal; - ActivityManagerInternal mAmInternal; - PackageManagerInternal mPackageManagerInternal; + private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = + new RemoteCallbackList<>(); public CompanionDeviceManagerService(Context context) { super(context); @@ -209,14 +156,12 @@ public class CompanionDeviceManagerService extends SystemService mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); - mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mPermissionControllerManager = requireNonNull( - context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); - LocalServices.addService(CompanionDeviceManagerServiceInternal.class, mLocalService); + mUserPersistenceHandler = new PersistUserStateHandler(); + mAssociationStore = new AssociationStoreImpl(); } @Override @@ -224,14 +169,24 @@ public class CompanionDeviceManagerService extends SystemService mPersistentStore = new PersistentDataStore(); loadAssociationsFromDisk(); - mAssociationStore.registerListener(this); + mAssociationStore.registerListener(mAssociationStoreChangeListener); + + mDevicePresenceMonitor = new CompanionDevicePresenceMonitor( + mAssociationStore, mDevicePresenceCallback); - mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); - mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore); + mAssociationRequestsProcessor = new AssociationRequestsProcessor( + /* cdmService */this, mAssociationStore); - // Publish "binder service" + final Context context = getContext(); + mCompanionAppController = new CompanionApplicationController( + context, mApplicationControllerCallback); + + // Publish "binder" service. final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); + + // Publish "local" service. + LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService()); } void loadAssociationsFromDisk() { @@ -248,21 +203,13 @@ public class CompanionDeviceManagerService extends SystemService @Override public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - registerPackageMonitor(); - - // Init Bluetooth - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mBluetoothAdapter != null) { - mBluetoothAdapter.registerBluetoothConnectionCallback( - getContext().getMainExecutor(), - mBluetoothDeviceConnectedListener); - getContext().registerReceiver( - mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter); - initBleScanning(); - } else { - Slog.w(LOG_TAG, "No BluetoothAdapter available"); - } + final Context context = getContext(); + if (phase == PHASE_SYSTEM_SERVICES_READY) { + // WARNING: moving PackageMonitor to another thread (Looper) may introduce significant + // delays (even in case of the Main Thread). It may be fine overall, but would require + // updating the tests (adding a delay there). + mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true); + mDevicePresenceMonitor.init(context); } else if (phase == PHASE_BOOT_COMPLETED) { // Run the Association CleanUp job service daily. AssociationCleanUpService.schedule(getContext()); @@ -288,76 +235,84 @@ public class CompanionDeviceManagerService extends SystemService @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, macAddress); - return sanitizeWithCallerChecks(association); + return sanitizeWithCallerChecks(getContext(), association); } @Nullable AssociationInfo getAssociationWithCallerChecks(int associationId) { final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - return sanitizeWithCallerChecks(association); + return sanitizeWithCallerChecks(getContext(), association); } - @Nullable - private AssociationInfo sanitizeWithCallerChecks(@Nullable AssociationInfo association) { - if (association == null) return null; + private void onDeviceAppearedInternal(int associationId) { + if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId); + + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (DEBUG) Log.d(TAG, " association=" + associationId); + + if (!association.shouldBindWhenPresent()) return; final int userId = association.getUserId(); final String packageName = association.getPackageName(); - if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) { - return null; - } - return association; + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + mCompanionAppController.bindCompanionApplication(userId, packageName); + } else if (DEBUG) { + Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); + } + mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association); } - // Revoke associations if the selfManaged companion device does not connect for 3 - // months for specific profile. - private void associationCleanUp(String profile) { - for (AssociationInfo ai : mAssociationStore.getAssociations()) { - if (ai.isSelfManaged() - && profile.equals(ai.getDeviceProfile()) - && System.currentTimeMillis() - ai.getLastTimeConnectedMs() - >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) { - Slog.d(LOG_TAG, "Removing the association for associationId: " - + ai.getId() - + " due to the device does not connect for 3 months." - + " Current time: " - + new Date(System.currentTimeMillis())); - disassociateInternal(ai.getId()); - } + private void onDeviceDisappearedInternal(int associationId) { + if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId); + + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (DEBUG) Log.d(TAG, " association=" + associationId); + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound"); + return; } + + if (association.shouldBindWhenPresent()) { + mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association); + } + + // Check if there are other devices associated to the app that are present. + if (shouldBindPackage(userId, packageName)) return; + + mCompanionAppController.unbindCompanionApplication(userId, packageName); } - void maybeGrantAutoRevokeExemptions() { - Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()"); - PackageManager pm = getContext().getPackageManager(); - for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { - SharedPreferences pref = getContext().getSharedPreferences( - new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), - Context.MODE_PRIVATE); - if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { - continue; - } + private boolean onCompanionApplicationBindingDiedInternal( + @UserIdInt int userId, @NonNull String packageName) { + // TODO(b/218613015): implement. + return false; + } - try { - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsForUser(userId); - for (AssociationInfo a : associations) { - try { - int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); - exemptFromAutoRevoke(a.getPackageName(), uid); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e); - } - } - } finally { - pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); - } + private void onRebindCompanionApplicationTimeoutInternal( + @UserIdInt int userId, @NonNull String packageName) { + // TODO(b/218613015): implement. + } + + /** + * @return whether the package should be bound (i.e. at least one of the devices associated with + * the package is currently present). + */ + private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { + final List<AssociationInfo> packageAssociations = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : packageAssociations) { + if (!association.shouldBindWhenPresent()) continue; + if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true; } + return false; } - @Override - public void onAssociationChanged( + private void onAssociationChangedInternal( @AssociationStore.ChangeType int changeType, AssociationInfo association) { final int id = association.getId(); final int userId = association.getUserId(); @@ -379,8 +334,6 @@ public class CompanionDeviceManagerService extends SystemService notifyListeners(userId, updatedAssociations); } updateAtm(userId, updatedAssociations); - - restartBleScan(); } private void persistStateForUser(@UserIdInt int userId) { @@ -417,15 +370,59 @@ public class CompanionDeviceManagerService extends SystemService } } - class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { + private void onPackageRemoveOrDataClearedInternal( + @UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) { + Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/" + + packageName); + } + + // Clear associations. + final List<AssociationInfo> associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + mAssociationStore.removeAssociation(association.getId()); + } + + mCompanionAppController.onPackagesChanged(userId); + } + + private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName); + + final List<AssociationInfo> associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + updateSpecialAccessPermissionForAssociatedPackage(association); + } + + mCompanionAppController.onPackagesChanged(userId); + } + + // Revoke associations if the selfManaged companion device does not connect for 3 + // months for specific profile. + private void associationCleanUp(String profile) { + for (AssociationInfo ai : mAssociationStore.getAssociations()) { + if (ai.isSelfManaged() + && profile.equals(ai.getDeviceProfile()) + && System.currentTimeMillis() - ai.getLastTimeConnectedMs() + >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) { + Slog.i(TAG, "Removing the association for associationId: " + + ai.getId() + + " due to the device does not connect for 3 months."); + disassociateInternal(ai.getId()); + } + } + } + class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { try { return super.onTransact(code, data, reply, flags); } catch (Throwable e) { - Slog.e(LOG_TAG, "Error during IPC", e); + Slog.e(TAG, "Error during IPC", e); throw ExceptionUtils.propagate(e, RemoteException.class); } } @@ -433,7 +430,7 @@ public class CompanionDeviceManagerService extends SystemService @Override public void associate(AssociationRequest request, IAssociationRequestCallback callback, String packageName, int userId) throws RemoteException { - Slog.i(LOG_TAG, "associate() " + Slog.i(TAG, "associate() " + "request=" + request + ", " + "package=u" + userId + "/" + packageName); enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, @@ -451,7 +448,7 @@ public class CompanionDeviceManagerService extends SystemService if (!checkCallerCanManageCompanionDevice(getContext())) { // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to // request the feature (also: the caller is the app itself). - checkUsesFeature(packageName, getCallingUserId()); + enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); } return mAssociationStore.getAssociationsForPackage(userId, packageName); @@ -487,6 +484,11 @@ public class CompanionDeviceManagerService extends SystemService @Override public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) { + if (DEBUG) { + Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName + + ", macAddress=" + deviceMacAddress); + } + requireNonNull(deviceMacAddress); requireNonNull(packageName); @@ -503,6 +505,8 @@ public class CompanionDeviceManagerService extends SystemService @Override public void disassociate(int associationId) { + if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId); + final AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " @@ -519,9 +523,9 @@ public class CompanionDeviceManagerService extends SystemService throws RemoteException { String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); - //TODO: check userId. + // TODO: check userId. String packageTitle = BidiFormatter.getInstance().unicodeWrap( - getPackageInfo(callingPackage, userId) + getPackageInfo(getContext(), userId, callingPackage) .applicationInfo .loadSafeLabel(getContext().getPackageManager(), PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, @@ -575,26 +579,28 @@ public class CompanionDeviceManagerService extends SystemService @Override public void registerDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId) throws RemoteException { - //TODO: take the userId into account. + // TODO: take the userId into account. registerDevicePresenceListenerActive(callingPackage, deviceAddress, true); } @Override public void unregisterDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId) throws RemoteException { - //TODO: take the userId into account. + // TODO: take the userId into account. registerDevicePresenceListenerActive(callingPackage, deviceAddress, false); } @Override public void dispatchMessage(int messageId, int associationId, byte[] message) throws RemoteException { - //TODO: b/199427116 + // TODO(b/199427116): implement. } @Override public void notifyDeviceAppeared(int associationId) { - final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId); + + AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " + "does not exist " @@ -607,23 +613,20 @@ public class CompanionDeviceManagerService extends SystemService + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + " self-managed associations."); } - - if (!mPresentSelfManagedDevices.add(associationId)) { - Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present"); - return; - } - - AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // timestamp. + association = AssociationInfo.builder(association) .setLastTimeConnected(System.currentTimeMillis()) .build(); - mAssociationStore.updateAssociation(updatedAssociationInfo); + mAssociationStore.updateAssociation(association); - mCompanionDevicePresenceController.onDeviceNotifyAppeared( - updatedAssociationInfo, getContext(), mMainHandler); + mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId); } @Override public void notifyDeviceDisappeared(int associationId) { + if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId); + final AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " @@ -638,14 +641,7 @@ public class CompanionDeviceManagerService extends SystemService + " self-managed associations."); } - if (!mPresentSelfManagedDevices.contains(associationId)) { - Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected"); - return; - } - - mPresentSelfManagedDevices.remove(associationId); - mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind( - association, getContext(), mMainHandler); + mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId); } private void registerDevicePresenceListenerActive(String packageName, String deviceAddress, @@ -656,8 +652,7 @@ public class CompanionDeviceManagerService extends SystemService final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, packageName); - final AssociationInfo association = - mAssociationStore.getAssociationsForPackageWithAddress( + AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, deviceAddress); if (association == null) { @@ -666,10 +661,14 @@ public class CompanionDeviceManagerService extends SystemService + " for user " + userId)); } - AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // flag. + association = AssociationInfo.builder(association) .setNotifyOnDeviceNearby(active) .build(); - mAssociationStore.updateAssociation(updatedAssociationInfo); + mAssociationStore.updateAssociation(association); + + // TODO(b/218615198): correctly handle the case when the device is currently present. } @Override @@ -677,7 +676,7 @@ public class CompanionDeviceManagerService extends SystemService byte[] certificate) { if (!getContext().getPackageManager().hasSigningCertificate( packageName, certificate, CERT_INPUT_SHA256)) { - Slog.e(LOG_TAG, "Given certificate doesn't match the package certificate."); + Slog.e(TAG, "Given certificate doesn't match the package certificate."); return; } @@ -691,10 +690,12 @@ public class CompanionDeviceManagerService extends SystemService final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, callingPackage); + if (getCallingUid() == SYSTEM_UID) return; + + enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage); checkState(!ArrayUtils.isEmpty( mAssociationStore.getAssociationsForPackage(userId, callingPackage)), "App must have an association before calling this API"); - checkUsesFeature(callingPackage, userId); } @Override @@ -720,47 +721,20 @@ public class CompanionDeviceManagerService extends SystemService } @Override - public void dump(@NonNull FileDescriptor fd, - @NonNull PrintWriter fout, + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter out, @Nullable String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, out)) { return; } - fout.append("Companion Device Associations:").append('\n'); + // TODO(b/218615185): mAssociationStore.dump() instead + out.append("Companion Device Associations:").append('\n'); for (AssociationInfo a : mAssociationStore.getAssociations()) { - fout.append(" ").append(a.toString()).append('\n'); + out.append(" ").append(a.toString()).append('\n'); } - fout.append("Currently Connected Devices:").append('\n'); - for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) { - fout.append(" ").append(mCurrentlyConnectedDevices.get(i)).append('\n'); - } - - fout.append("Currently SelfManaged Connected Devices associationId:").append('\n'); - for (Integer associationId : mPresentSelfManagedDevices) { - fout.append(" ").append("AssociationId: ").append( - String.valueOf(associationId)).append('\n'); - } - - fout.append("Devices Last Nearby:").append('\n'); - for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) { - String device = mDevicesLastNearby.keyAt(i); - Date time = mDevicesLastNearby.valueAt(i); - fout.append(" ").append(device).append(" -> ") - .append(sDateFormat.format(time)).append('\n'); - } - - fout.append("Device Listener Services State:").append('\n'); - for (int i = 0, size = mCompanionDevicePresenceController.mBoundServices.size(); - i < size; i++) { - int userId = mCompanionDevicePresenceController.mBoundServices.keyAt(i); - fout.append(" ") - .append("u").append(Integer.toString(userId)).append(": ") - .append(Objects.toString( - mCompanionDevicePresenceController.mBoundServices.valueAt(i))) - .append('\n'); - } + // TODO(b/218615185): mDevicePresenceMonitor.dump() + // TODO(b/218615185): mCompanionAppController.dump() } } @@ -784,7 +758,7 @@ public class CompanionDeviceManagerService extends SystemService final AssociationInfo association = new AssociationInfo(id, userId, packageName, macAddress, displayName, deviceProfile, selfManaged, false, timestamp, Long.MAX_VALUE); - Slog.i(LOG_TAG, "New CDM association created=" + association); + Slog.i(TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); // If the "Device Profile" is specified, make the companion application a holder of the @@ -862,52 +836,50 @@ public class CompanionDeviceManagerService extends SystemService } } - //TODO: also revoke notification access + // TODO: also revoke notification access void disassociateInternal(int associationId) { - onAssociationPreRemove(associationId); + final AssociationInfo association = mAssociationStore.getAssociationById(associationId); + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + final String deviceProfile = association.getDeviceProfile(); + + final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId); + + // Removing the association. mAssociationStore.removeAssociation(associationId); - } - void onAssociationPreRemove(int associationId) { - final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - if (association.isNotifyOnDeviceNearby() - || (association.isSelfManaged() - && mPresentSelfManagedDevices.contains(association.getId()))) { - mCompanionDevicePresenceController.unbindDevicePresenceListener( - association.getPackageName(), association.getUserId()); - } + final List<AssociationInfo> otherAssociations = + mAssociationStore.getAssociationsForPackage(userId, packageName); - String deviceProfile = association.getDeviceProfile(); + // Check if the package is associated with other devices with the same profile. + // If not: take away the role. if (deviceProfile != null) { - AssociationInfo otherAssociationWithDeviceProfile = find( - mAssociationStore.getAssociationsForUser(association.getUserId()), - a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile())); - if (otherAssociationWithDeviceProfile != null) { - Slog.i(LOG_TAG, "Not revoking " + deviceProfile - + " for " + association - + " - profile still present in " + otherAssociationWithDeviceProfile); - } else { - Binder.withCleanCallingIdentity( - () -> removeRoleHolderForAssociation(getContext(), association)); + final boolean shouldKeepTheRole = any(otherAssociations, + it -> deviceProfile.equals(it.getDeviceProfile())); + if (!shouldKeepTheRole) { + Binder.withCleanCallingIdentity(() -> + removeRoleHolderForAssociation(getContext(), association)); } } + + if (!wasPresent || !association.isNotifyOnDeviceNearby()) return; + // The device was connected and the app was notified: check if we need to unbind the app + // now. + final boolean shouldStayBound = any(otherAssociations, + it -> it.isNotifyOnDeviceNearby() + && mDevicePresenceMonitor.isDevicePresent(it.getId())); + if (shouldStayBound) return; + mCompanionAppController.unbindCompanionApplication(userId, packageName); } private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { - PackageInfo packageInfo = getPackageInfo( - association.getPackageName(), - association.getUserId()); - if (packageInfo == null) { - return; - } + final PackageInfo packageInfo = + getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); - Binder.withCleanCallingIdentity(obtainRunnable(CompanionDeviceManagerService:: - updateSpecialAccessPermissionAsSystem, this, association, packageInfo) - .recycleOnUse()); + Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); } - private void updateSpecialAccessPermissionAsSystem( - AssociationInfo association, PackageInfo packageInfo) { + private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { if (containsEither(packageInfo.requestedPermissions, android.Manifest.permission.RUN_IN_BACKGROUND, android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { @@ -916,7 +888,7 @@ public class CompanionDeviceManagerService extends SystemService try { mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName); } catch (UnsupportedOperationException e) { - Slog.w(LOG_TAG, packageInfo.packageName + " can't be removed from power save" + Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" + " whitelist. It might due to the package is whitelisted by the system."); } } @@ -935,10 +907,6 @@ public class CompanionDeviceManagerService extends SystemService } exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - - if (association.isNotifyOnDeviceNearby()) { - restartBleScan(); - } } private void exemptFromAutoRevoke(String packageName, int uid) { @@ -949,23 +917,10 @@ public class CompanionDeviceManagerService extends SystemService packageName, AppOpsManager.MODE_IGNORED); } catch (RemoteException e) { - Slog.w(LOG_TAG, - "Error while granting auto revoke exemption for " + packageName, e); + Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e); } } - private static <T> boolean containsEither(T[] array, T a, T b) { - return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); - } - - @Nullable - private PackageInfo getPackageInfo(String packageName, int userId) { - final int flags = PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS; - return Binder.withCleanCallingIdentity( - () -> getContext().getPackageManager() - .getPackageInfoAsUser(packageName, flags , userId)); - } - private void updateAtm(int userId, List<AssociationInfo> associations) { final Set<Integer> companionAppUids = new ArraySet<>(); for (AssociationInfo association : associations) { @@ -981,262 +936,85 @@ public class CompanionDeviceManagerService extends SystemService } } - void onDeviceConnected(String address) { - Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); - mCurrentlyConnectedDevices.add(address); - onDeviceNearby(address); - } - - void onDeviceDisconnected(String address) { - Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")"); + private void maybeGrantAutoRevokeExemptions() { + Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); - mCurrentlyConnectedDevices.remove(address); + PackageManager pm = getContext().getPackageManager(); + for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { + SharedPreferences pref = getContext().getSharedPreferences( + new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), + Context.MODE_PRIVATE); + if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { + continue; + } - Date lastSeen = mDevicesLastNearby.get(address); - if (isDeviceDisappeared(lastSeen)) { - onDeviceDisappeared(address); - unscheduleTriggerDeviceDisappearedRunnable(address); + try { + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsForUser(userId); + for (AssociationInfo a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); + exemptFromAutoRevoke(a.getPackageName(), uid); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); + } + } + } finally { + pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); + } } } - private boolean isDeviceDisappeared(Date lastSeen) { - return lastSeen == null || System.currentTimeMillis() - lastSeen.getTime() - >= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS; - } - - private class BleScanCallback extends ScanCallback { + private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = + new AssociationStore.OnChangeListener() { @Override - public void onScanResult(int callbackType, ScanResult result) { - if (DEBUG) { - Slog.i(LOG_TAG, "onScanResult(callbackType = " - + callbackType + ", result = " + result + ")"); - } - - onDeviceNearby(result.getDevice().getAddress()); + public void onAssociationChanged(int changeType, AssociationInfo association) { + onAssociationChangedInternal(changeType, association); } + }; + private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback = + new CompanionDevicePresenceMonitor.Callback() { @Override - public void onBatchScanResults(List<ScanResult> results) { - for (int i = 0, size = results.size(); i < size; i++) { - onScanResult(CALLBACK_TYPE_ALL_MATCHES, results.get(i)); - } + public void onDeviceAppeared(int associationId) { + onDeviceAppearedInternal(associationId); } @Override - public void onScanFailed(int errorCode) { - if (errorCode == SCAN_FAILED_ALREADY_STARTED) { - // ignore - this might happen if BT tries to auto-restore scans for us in the - // future - Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED"); - } else { - Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode); - } + public void onDeviceDisappeared(int associationId) { + onDeviceDisappearedInternal(associationId); } - } - - private class BleStateBroadcastReceiver extends BroadcastReceiver { - - final IntentFilter mIntentFilter = - new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); + }; + private final CompanionApplicationController.Callback mApplicationControllerCallback = + new CompanionApplicationController.Callback() { @Override - public void onReceive(Context context, Intent intent) { - int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1); - int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - Slog.d(LOG_TAG, "Received BT state transition broadcast: " - + BluetoothAdapter.nameForState(previousState) - + " -> " + BluetoothAdapter.nameForState(newState)); - - boolean bleOn = newState == BluetoothAdapter.STATE_ON - || newState == BluetoothAdapter.STATE_BLE_ON; - if (bleOn) { - if (mBluetoothAdapter.getBluetoothLeScanner() != null) { - startBleScan(); - } else { - Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null"); - } - } - } - } - - private class UnbindDeviceListenersRunnable implements Runnable { - - public String getJobId(String address) { - return "CDM_deviceGone_unbind_" + address; + public boolean onCompanionApplicationBindingDied(int userId, @NonNull String packageName) { + return onCompanionApplicationBindingDiedInternal(userId, packageName); } @Override - public void run() { - int size = mDevicesLastNearby.size(); - for (int i = 0; i < size; i++) { - String address = mDevicesLastNearby.keyAt(i); - Date lastNearby = mDevicesLastNearby.valueAt(i); - - if (isDeviceDisappeared(lastNearby)) { - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.unbindDevicePresenceListener( - association.getPackageName(), association.getUserId()); - } - } - } - } - } - } - - private class TriggerDeviceDisappearedRunnable implements Runnable { - - private final String mAddress; - - TriggerDeviceDisappearedRunnable(String address) { - mAddress = address; - } - - public void schedule() { - mMainHandler.removeCallbacks(this); - mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS); + public void onRebindCompanionApplicationTimeout(int userId, @NonNull String packageName) { + onRebindCompanionApplicationTimeoutInternal(userId, packageName); } + }; + private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override - public void run() { - Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")"); - if (!mCurrentlyConnectedDevices.contains(mAddress)) { - onDeviceDisappeared(mAddress); - } - } - } - - private void unscheduleTriggerDeviceDisappearedRunnable(String address) { - Runnable r = mTriggerDeviceDisappearedRunnables.get(address); - if (r != null) { - Slog.d(LOG_TAG, - "unscheduling TriggerDeviceDisappearedRunnable(address = " + address + ")"); - mMainHandler.removeCallbacks(r); - } - } - - private void onDeviceNearby(String address) { - Date timestamp = new Date(); - Date oldTimestamp = mDevicesLastNearby.put(address, timestamp); - - cancelUnbindDeviceListener(address); - - mTriggerDeviceDisappearedRunnables - .computeIfAbsent(address, addr -> new TriggerDeviceDisappearedRunnable(address)) - .schedule(); - - // Avoid spamming the app if device is already known to be nearby - boolean justAppeared = oldTimestamp == null - || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS; - if (justAppeared) { - Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")"); - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.onDeviceNotifyAppeared(association, - getContext(), mMainHandler); - } - } - } - } - - private void onDeviceDisappeared(String address) { - Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")"); - - boolean hasDeviceListeners = false; - final List<AssociationInfo> associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.onDeviceNotifyDisappeared( - association, getContext(), mMainHandler); - hasDeviceListeners = true; - } - } - - cancelUnbindDeviceListener(address); - if (hasDeviceListeners) { - mMainHandler.postDelayed( - mUnbindDeviceListenersRunnable, - mUnbindDeviceListenersRunnable.getJobId(address), - DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS); - } - } - - private void cancelUnbindDeviceListener(String address) { - mMainHandler.removeCallbacks( - mUnbindDeviceListenersRunnable, mUnbindDeviceListenersRunnable.getJobId(address)); - } - - private void initBleScanning() { - Slog.i(LOG_TAG, "initBleScanning()"); - - boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback( - new BluetoothAdapter.ServiceLifecycleCallback() { - @Override - public void onBluetoothServiceUp() { - Slog.i(LOG_TAG, "Bluetooth stack is up"); - startBleScan(); - } - - @Override - public void onBluetoothServiceDown() { - Slog.w(LOG_TAG, "Bluetooth stack is down"); - } - }); - if (bluetoothReady) { - startBleScan(); - } - } - - void startBleScan() { - Slog.i(LOG_TAG, "startBleScan()"); - - List<ScanFilter> filters = getBleScanFilters(); - if (filters.isEmpty()) { - return; + public void onPackageRemoved(String packageName, int uid) { + onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); } - BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); - if (scanner == null) { - Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)"); - } else { - scanner.startScan( - filters, - new ScanSettings.Builder().setScanMode(SCAN_MODE_LOW_POWER).build(), - mBleScanCallback); - } - } - void restartBleScan() { - if (mBluetoothAdapter.getBluetoothLeScanner() != null) { - mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); - startBleScan(); - } else { - Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet)."); + @Override + public void onPackageDataCleared(String packageName, int uid) { + onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); } - } - private List<ScanFilter> getBleScanFilters() { - ArrayList<ScanFilter> result = new ArrayList<>(); - ArraySet<String> addressesSeen = new ArraySet<>(); - for (AssociationInfo association : mAssociationStore.getAssociations()) { - if (association.isSelfManaged()) { - continue; - } - String address = association.getDeviceMacAddressAsString(); - if (addressesSeen.contains(address)) { - continue; - } - if (association.isNotifyOnDeviceNearby()) { - result.add(new ScanFilter.Builder().setDeviceAddress(address).build()); - addressesSeen.add(address); - } + @Override + public void onPackageModified(String packageName) { + onPackageModifiedInternal(getChangingUserId(), packageName); } - return result; - } + }; static int getFirstAssociationIdForUser(@UserIdInt int userId) { // We want the IDs to start from 1, not 0. @@ -1247,82 +1025,6 @@ public class CompanionDeviceManagerService extends SystemService return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE; } - private class BluetoothDeviceConnectedListener - extends BluetoothAdapter.BluetoothConnectionCallback { - @Override - public void onDeviceConnected(BluetoothDevice device) { - CompanionDeviceManagerService.this.onDeviceConnected(device.getAddress()); - } - - @Override - public void onDeviceDisconnected(BluetoothDevice device, int reason) { - Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") " - + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason)); - CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress()); - } - } - - void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) { - if (getCallingUid() == SYSTEM_UID) return; - - final FeatureInfo[] requestedFeatures = getPackageInfo(pkg, userId).reqFeatures; - if (requestedFeatures != null) { - for (int i = 0; i < requestedFeatures.length; i++) { - if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeatures[i].name)) return; - } - } - - throw new IllegalStateException("Must declare uses-feature " - + FEATURE_COMPANION_DEVICE_SETUP - + " in manifest to use this API"); - } - - private void registerPackageMonitor() { - new PackageMonitor() { - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } - - @Override - public void onPackageDataCleared(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } - - @Override - public void onPackageModified(String packageName) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName); - - final List<AssociationInfo> associationsForPackage = - mAssociationStore.getAssociationsForPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - updateSpecialAccessPermissionForAssociatedPackage(association); - } - } - }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); - } - - private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) { - if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName); - - // First, unbind CompanionService if needed. - mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId); - - // Clear associations. - final List<AssociationInfo> associationsForPackage = - mAssociationStore.getAssociationsForPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - mAssociationStore.removeAssociation(association.getId()); - } - } - private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) { final Map<String, Set<Integer>> copy = new HashMap<>(); @@ -1334,16 +1036,14 @@ public class CompanionDeviceManagerService extends SystemService return Collections.unmodifiableMap(copy); } - private final class LocalService extends CompanionDeviceManagerServiceInternal { - private final CompanionDeviceManagerService mService; - - LocalService(CompanionDeviceManagerService service) { - mService = service; - } + private static <T> boolean containsEither(T[] array, T a, T b) { + return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); + } + private class LocalService extends CompanionDeviceManagerServiceInternal { @Override public void associationCleanUp(String profile) { - mService.associationCleanUp(profile); + CompanionDeviceManagerService.this.associationCleanUp(profile); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java deleted file mode 100644 index fc6681705cb6..000000000000 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion; - -import static android.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE; -import static android.content.Context.BIND_IMPORTANT; - -import static com.android.internal.util.CollectionUtils.filter; - -import android.annotation.NonNull; -import android.companion.AssociationInfo; -import android.companion.CompanionDeviceService; -import android.companion.ICompanionDeviceService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Handler; -import android.util.ArrayMap; -import android.util.Slog; - -import com.android.internal.infra.PerUser; -import com.android.internal.infra.ServiceConnector; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and - * the companion apps. The controller also will notify the companion apps with device status. - */ -public class CompanionDevicePresenceController { - private static final String LOG_TAG = "CompanionDevicePresenceController"; - PerUser<ArrayMap<String, List<BoundService>>> mBoundServices; - private static final String META_DATA_KEY_PRIMARY = "android.companion.primary"; - private final CompanionDeviceManagerService mService; - - public CompanionDevicePresenceController(CompanionDeviceManagerService service) { - mService = service; - mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() { - @NonNull - @Override - protected ArrayMap<String, List<BoundService>> create(int userId) { - return new ArrayMap<>(); - } - }; - } - - void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceAppeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.run( - service -> service.onDeviceAppeared(association)); - } else { - Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName); - boundService.mServiceConnector.connect(); - } - } - } - - void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.run(service -> - service.onDeviceDisappeared(association)); - } - } - } - - void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association, - Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.post( - service -> { - service.onDeviceDisappeared(association); - }).thenRun(() -> unbindDevicePresenceListener( - association.getPackageName(), association.getUserId())); - } - } - } - - void unbindDevicePresenceListener(String packageName, int userId) { - List<BoundService> boundServices = mBoundServices.forUser(userId) - .remove(packageName); - if (boundServices != null) { - for (BoundService boundService: boundServices) { - Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName); - boundService.mServiceConnector.unbind(); - } - } - } - - private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context, - Handler handler) { - return mBoundServices.forUser(a.getUserId()).computeIfAbsent( - a.getPackageName(), - pkg -> createDeviceListenerServiceConnector(a, context, handler)); - } - - private List<BoundService> createDeviceListenerServiceConnector(AssociationInfo a, - Context context, Handler handler) { - List<ResolveInfo> resolveInfos = context - .getPackageManager() - .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE), - PackageManager.GET_META_DATA, a.getUserId()); - List<ResolveInfo> packageResolveInfos = filter(resolveInfos, - info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName())); - List<BoundService> serviceConnectors = new ArrayList<>(); - if (!validatePackageInfo(packageResolveInfos, a)) { - return serviceConnectors; - } - for (ResolveInfo packageResolveInfo : packageResolveInfos) { - boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null - && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) - || packageResolveInfos.size() == 1; - ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName(); - - Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName); - - ServiceConnector<ICompanionDeviceService> serviceConnector = - new ServiceConnector.Impl<ICompanionDeviceService>(context, - new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent( - componentName), BIND_IMPORTANT, a.getUserId(), - ICompanionDeviceService.Stub::asInterface) { - @Override - protected long getAutoDisconnectTimeoutMs() { - // Service binding is managed manually based on corresponding device - // being nearby - return -1; - } - - @Override - public void binderDied() { - super.binderDied(); - if (a.isSelfManaged()) { - mBoundServices.forUser(a.getUserId()).remove(a.getPackageName()); - mService.mPresentSelfManagedDevices.remove(a.getId()); - } else { - // Re-connect to the service if process gets killed - handler.postDelayed( - this::connect, - CompanionDeviceManagerService - .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS); - } - } - }; - - serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector)); - } - return serviceConnectors; - } - - private boolean validatePackageInfo(List<ResolveInfo> packageResolveInfos, - AssociationInfo association) { - if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) { - Slog.e(LOG_TAG, "Device presence listener package must have at least one and not " - + "more than five CompanionDeviceService(s) declared. But " - + association.getPackageName() - + " has " + packageResolveInfos.size()); - return false; - } - - int primaryCount = 0; - for (ResolveInfo packageResolveInfo : packageResolveInfos) { - String servicePermission = packageResolveInfo.serviceInfo.permission; - if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) { - Slog.e(LOG_TAG, "Binding CompanionDeviceService must have " - + BIND_COMPANION_DEVICE_SERVICE + " permission."); - return false; - } - - if (packageResolveInfo.serviceInfo.metaData != null - && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) { - primaryCount++; - if (primaryCount > 1) { - Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " - + "to be bound but " - + association.getPackageName() + "has " + primaryCount); - return false; - } - } - } - - if (packageResolveInfos.size() > 1 && primaryCount == 0) { - Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " - + "to be bound when declare more than one CompanionDeviceService but " - + association.getPackageName() + " has " + primaryCount); - return false; - } - - if (packageResolveInfos.size() == 1 && primaryCount != 0) { - Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one" - + " CompanionDeviceService " + "but " + association.getPackageName() - + " has " + primaryCount); - } - - return true; - } - - private static class BoundService { - private final ComponentName mComponentName; - private final boolean mIsPrimary; - private final ServiceConnector<ICompanionDeviceService> mServiceConnector; - - BoundService(ComponentName componentName, - boolean isPrimary, ServiceConnector<ICompanionDeviceService> serviceConnector) { - this.mComponentName = componentName; - this.mIsPrimary = isPrimary; - this.mServiceConnector = serviceConnector; - } - } -} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index 777917cd5a9e..f2a58b74a65a 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -17,6 +17,7 @@ package com.android.server.companion; import static android.content.Context.BIND_IMPORTANT; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,10 +29,12 @@ import android.companion.ICompanionDeviceService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.Handler; import android.os.IBinder; import android.util.Log; import com.android.internal.infra.ServiceConnector; +import com.android.server.ServiceThread; /** * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the @@ -106,6 +109,19 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe return ICompanionDeviceService.Stub.asInterface(service); } + /** + * Overrides {@link ServiceConnector.Impl#getJobHandler()} to provide an alternative Thread + * ("in form of" a {@link Handler}) to process jobs on. + * <p> + * (By default, {@link ServiceConnector.Impl} process jobs on the + * {@link android.os.Looper#getMainLooper() MainThread} which is a shared singleton thread + * within system_server and thus tends to get heavily congested) + */ + @Override + protected @NonNull Handler getJobHandler() { + return getServiceThread().getThreadHandler(); + } + @Override protected long getAutoDisconnectTimeoutMs() { // Do NOT auto-disconnect. @@ -116,4 +132,25 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe return new Intent(CompanionDeviceService.SERVICE_INTERFACE) .setComponent(componentName); } + + private static @NonNull ServiceThread getServiceThread() { + if (sServiceThread == null) { + synchronized (CompanionDeviceManagerService.class) { + if (sServiceThread == null) { + sServiceThread = new ServiceThread("companion-device-service-connector", + THREAD_PRIORITY_DEFAULT, /* allowIo */ false); + sServiceThread.start(); + } + } + } + return sServiceThread; + } + + /** + * A worker thread for the {@link ServiceConnector} to process jobs on. + * + * <p> + * Do NOT reference directly, use {@link #getServiceThread()} method instead. + */ + private static volatile @Nullable ServiceThread sServiceThread; } diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java index fcb14a4f04d0..818f0cf8dd42 100644 --- a/services/companion/java/com/android/server/companion/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/PackageUtils.java @@ -21,7 +21,7 @@ import static android.content.pm.PackageManager.GET_CONFIGURATIONS; import static android.content.pm.PackageManager.GET_META_DATA; import static android.content.pm.PackageManager.GET_PERMISSIONS; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; import android.Manifest; import android.annotation.NonNull; @@ -96,7 +96,7 @@ final class PackageUtils { final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE .equals(resolveInfo.serviceInfo.permission); if (!requiresPermission) { - Slog.w(LOG_TAG, "CompanionDeviceService " + Slog.w(TAG, "CompanionDeviceService " + service.getComponentName().flattenToShortString() + " must require " + "android.permission.BIND_COMPANION_DEVICE_SERVICE"); continue; diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java index 0e593e14a037..ac1bf1bd8c23 100644 --- a/services/companion/java/com/android/server/companion/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java @@ -36,6 +36,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.content.Context; @@ -190,6 +191,19 @@ final class PermissionsUtils { return checkCallerCanManageCompanionDevice(context); } + static @Nullable AssociationInfo sanitizeWithCallerChecks(@NonNull Context context, + @Nullable AssociationInfo association) { + if (association == null) return null; + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) { + return null; + } + + return association; + } + private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) { try { return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED; diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java index 904283f4e60e..35488a80b78b 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -35,7 +36,6 @@ import java.util.List; /** Utility methods for accessing {@link RoleManager} APIs. */ @SuppressLint("LongLogTag") final class RolesUtils { - private static final String TAG = CompanionDeviceManagerService.LOG_TAG; static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @NonNull String role) { diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index dafcc60fb651..4afa96c8072d 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -47,9 +47,17 @@ import java.util.function.Consumer; /** * A controller to control the policies of the windows that can be displayed on the virtual display. */ -class GenericWindowPolicyController extends DisplayWindowPolicyController { +public class GenericWindowPolicyController extends DisplayWindowPolicyController { - private static final String TAG = "VirtualDeviceManager"; + private static final String TAG = "GenericWindowPolicyController"; + + /** Interface to listen running applications change on virtual display. */ + public interface RunningAppsChangedListener { + /** + * Notifies the running applications change. + */ + void onRunningAppsChanged(ArraySet<Integer> runningUids); + } private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT = new ComponentName("android", BlockedAppStreamingActivity.class.getName()); @@ -74,6 +82,9 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Nullable private final ActivityListener mActivityListener; private final Handler mHandler = new Handler(Looper.getMainLooper()); + @Nullable + private RunningAppsChangedListener mRunningAppsChangedListener; + /** * Creates a window policy controller that is generic to the different use cases of virtual * device. @@ -84,7 +95,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { * @param activityListener Activity listener to listen for activity changes. The display ID * is not populated in this callback and is always {@link Display#INVALID_DISPLAY}. */ - GenericWindowPolicyController(int windowFlags, int systemWindowFlags, + public GenericWindowPolicyController(int windowFlags, int systemWindowFlags, @NonNull ArraySet<UserHandle> allowedUsers, @Nullable Set<ComponentName> allowedActivities, @Nullable Set<ComponentName> blockedActivities, @@ -98,6 +109,11 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { mActivityListener = activityListener; } + /** Sets listener for running applications change. */ + public void setRunningAppsChangedListener(@Nullable RunningAppsChangedListener listener) { + mRunningAppsChangedListener = listener; + } + @Override public boolean canContainActivities(@NonNull List<ActivityInfo> activities) { // Can't display all the activities if any of them don't want to be displayed. @@ -139,6 +155,9 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { // Post callback on the main thread so it doesn't block activity launching mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY)); } + if (mRunningAppsChangedListener != null) { + mRunningAppsChangedListener.onRunningAppsChanged(runningUids); + } } /** diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 075d96dd7ad4..3fa1c865bda1 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.app.Activity; import android.app.ActivityOptions; import android.app.PendingIntent; @@ -32,6 +33,7 @@ import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.audio.IAudioSessionCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -60,6 +62,7 @@ import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.BlockedAppStreamingActivity; +import com.android.server.companion.virtual.audio.VirtualAudioController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -78,6 +81,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; private final InputController mInputController; + private VirtualAudioController mVirtualAudioController; @VisibleForTesting final Set<Integer> mVirtualDisplayIds = new ArraySet<>(); private final OnDeviceCloseListener mListener; @@ -245,6 +249,46 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub close(); } + @VisibleForTesting + VirtualAudioController getVirtualAudioControllerForTesting() { + return mVirtualAudioController; + } + + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @Override // Binder call + public void onAudioSessionStarting(int displayId, IAudioSessionCallback callback) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to start audio session"); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplayIds.contains(displayId)) { + throw new SecurityException( + "Cannot start audio session for a display not associated with this virtual " + + "device"); + } + + if (mVirtualAudioController == null) { + mVirtualAudioController = new VirtualAudioController(mContext); + GenericWindowPolicyController gwpc = mWindowPolicyControllers.get(displayId); + mVirtualAudioController.startListening(gwpc, callback); + } + } + } + + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @Override // Binder call + public void onAudioSessionEnded() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to stop audio session"); + synchronized (mVirtualDeviceLock) { + if (mVirtualAudioController != null) { + mVirtualAudioController.stopListening(); + mVirtualAudioController = null; + } + } + } + @Override // Binder call public void createVirtualKeyboard( int displayId, diff --git a/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java b/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java new file mode 100644 index 000000000000..2d7291300e23 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/audio/AudioPlaybackDetector.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 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.companion.virtual.audio; + +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; + +import java.util.List; + +/** + * Wrapper class for other classes to listen {@link #onPlaybackConfigChanged(List)} by implementing + * {@link AudioPlaybackCallback} instead of inheriting the + * {@link AudioManager.AudioPlaybackCallback}. + */ +final class AudioPlaybackDetector extends AudioManager.AudioPlaybackCallback { + + /** + * Interface to listen {@link #onPlaybackConfigChanged(List)} from + * {@link AudioManager.AudioPlaybackCallback}. + */ + interface AudioPlaybackCallback { + void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs); + } + + private final AudioManager mAudioManager; + private AudioPlaybackCallback mAudioPlaybackCallback; + + AudioPlaybackDetector(Context context) { + mAudioManager = context.getSystemService(AudioManager.class); + } + + void register(@NonNull AudioPlaybackCallback callback) { + mAudioPlaybackCallback = callback; + mAudioManager.registerAudioPlaybackCallback(/* cb= */ this, /* handler= */ null); + } + + void unregister() { + mAudioPlaybackCallback = null; + mAudioManager.unregisterAudioPlaybackCallback(/* cb= */ this); + } + + @Override + public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { + super.onPlaybackConfigChanged(configs); + if (mAudioPlaybackCallback != null) { + mAudioPlaybackCallback.onPlaybackConfigChanged(configs); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java b/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java new file mode 100644 index 000000000000..c20414529f5b --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/audio/AudioRecordingDetector.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 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.companion.virtual.audio; + +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioRecordingConfiguration; + +import java.util.List; + +/** + * Wrapper class for other classes to listen {@link #onRecordingConfigChanged(List)} by implementing + * {@link AudioRecordingCallback} instead of inheriting the + * {@link AudioManager.AudioRecordingCallback}. + */ +final class AudioRecordingDetector extends AudioManager.AudioRecordingCallback { + + /** + * Interface to listen {@link #onRecordingConfigChanged(List)} from + * {@link AudioManager.AudioRecordingCallback}. + */ + interface AudioRecordingCallback { + void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs); + } + + private final AudioManager mAudioManager; + private AudioRecordingCallback mAudioRecordingCallback; + + AudioRecordingDetector(Context context) { + mAudioManager = context.getSystemService(AudioManager.class); + } + + void register(@NonNull AudioRecordingCallback callback) { + mAudioRecordingCallback = callback; + mAudioManager.registerAudioRecordingCallback(/* cb= */ this, /* handler= */ null); + } + + void unregister() { + mAudioRecordingCallback = null; + mAudioManager.unregisterAudioRecordingCallback(/* cb= */ this); + } + + @Override + public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { + super.onRecordingConfigChanged(configs); + if (mAudioRecordingCallback != null) { + mAudioRecordingCallback.onRecordingConfigChanged(configs); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java new file mode 100644 index 000000000000..1dc87d68aeae --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2022 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.companion.virtual.audio; + +import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.companion.virtual.audio.IAudioSessionCallback; +import android.content.Context; +import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.ArraySet; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.companion.virtual.GenericWindowPolicyController; +import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener; +import com.android.server.companion.virtual.audio.AudioPlaybackDetector.AudioPlaybackCallback; +import com.android.server.companion.virtual.audio.AudioRecordingDetector.AudioRecordingCallback; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages audio streams associated with a {@link VirtualAudioDevice}. Responsible for monitoring + * running applications and playback configuration changes in order to correctly re-route audio and + * then notify clients of these changes. + */ +public final class VirtualAudioController implements AudioPlaybackCallback, + AudioRecordingCallback, RunningAppsChangedListener { + private static final String TAG = "VirtualAudioController"; + private static final int UPDATE_REROUTING_APPS_DELAY_MS = 2000; + + private final Context mContext; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Runnable mUpdateAudioRoutingRunnable = this::notifyAppsNeedingAudioRoutingChanged; + private final AudioPlaybackDetector mAudioPlaybackDetector; + private final AudioRecordingDetector mAudioRecordingDetector; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final ArraySet<Integer> mRunningAppUids = new ArraySet<>(); + @GuardedBy("mLock") + private ArraySet<Integer> mPlayingAppUids = new ArraySet<>(); + private GenericWindowPolicyController mGenericWindowPolicyController; + private final Object mCallbackLock = new Object(); + @Nullable + @GuardedBy("mCallbackLock") + private IAudioSessionCallback mCallback; + + public VirtualAudioController(Context context) { + mContext = context; + mAudioPlaybackDetector = new AudioPlaybackDetector(context); + mAudioRecordingDetector = new AudioRecordingDetector(context); + } + + /** + * Starts to listen to running applications and audio configuration changes on virtual display + * for audio capture and injection. + */ + public void startListening( + @NonNull GenericWindowPolicyController genericWindowPolicyController, + @Nullable IAudioSessionCallback callback) { + mGenericWindowPolicyController = genericWindowPolicyController; + mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ this); + synchronized (mCallbackLock) { + mCallback = callback; + } + synchronized (mLock) { + mRunningAppUids.clear(); + mPlayingAppUids.clear(); + } + mAudioPlaybackDetector.register(/* callback= */ this); + mAudioRecordingDetector.register(/* callback= */ this); + } + + /** + * Stops listening to running applications and audio configuration changes on virtual display + * for audio capture and injection. + */ + public void stopListening() { + if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) { + mHandler.removeCallbacks(mUpdateAudioRoutingRunnable); + } + mAudioPlaybackDetector.unregister(); + mAudioRecordingDetector.unregister(); + if (mGenericWindowPolicyController != null) { + mGenericWindowPolicyController.setRunningAppsChangedListener(/* listener= */ null); + mGenericWindowPolicyController = null; + } + synchronized (mCallbackLock) { + mCallback = null; + } + } + + @Override + public void onRunningAppsChanged(ArraySet<Integer> runningUids) { + synchronized (mLock) { + if (mRunningAppUids.equals(runningUids)) { + // Ignore no-op events. + return; + } + mRunningAppUids.clear(); + mRunningAppUids.addAll(runningUids); + + ArraySet<Integer> oldPlayingAppUids = mPlayingAppUids; + + // Update the list of playing apps after caching the old list, and before checking if + // the list of playing apps is empty. This is a subset of the running apps, so we need + // to update this here as well. + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + List<AudioPlaybackConfiguration> configs = + audioManager.getActivePlaybackConfigurations(); + mPlayingAppUids = findPlayingAppUids(configs, mRunningAppUids); + + // Do not change rerouted applications while any application is playing, or the sound + // will be leaked from phone during the transition. Delay the change until we detect + // there is no application is playing in onPlaybackConfigChanged(). + if (!mPlayingAppUids.isEmpty()) { + Slog.i(TAG, "Audio is playing, do not change rerouted apps"); + return; + } + + // An application previously playing audio was removed from the display. + if (!oldPlayingAppUids.isEmpty()) { + // Delay changing the rerouted application when the last application playing audio + // was removed from virtual device, or the sound will be leaked from phone side + // during the transition. + Slog.i(TAG, "The last playing app removed, delay change rerouted apps"); + if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) { + mHandler.removeCallbacks(mUpdateAudioRoutingRunnable); + } + mHandler.postDelayed(mUpdateAudioRoutingRunnable, UPDATE_REROUTING_APPS_DELAY_MS); + return; + } + } + + // Normal case with no application playing, just update routing. + notifyAppsNeedingAudioRoutingChanged(); + } + + @Override + public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { + updatePlayingApplications(configs); + + List<AudioPlaybackConfiguration> audioPlaybackConfigurations; + synchronized (mLock) { + // Filter configurations of applications running on virtual device. + audioPlaybackConfigurations = findPlaybackConfigurations(configs, mRunningAppUids); + } + synchronized (mCallbackLock) { + if (mCallback != null) { + try { + mCallback.onPlaybackConfigChanged(audioPlaybackConfigurations); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when calling onPlaybackConfigChanged", e); + } + } + } + } + + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Override + public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { + List<AudioRecordingConfiguration> audioRecordingConfigurations; + synchronized (mLock) { + // Filter configurations of applications running on virtual device. + audioRecordingConfigurations = findRecordingConfigurations(configs, mRunningAppUids); + } + synchronized (mCallbackLock) { + if (mCallback != null) { + try { + mCallback.onRecordingConfigChanged(audioRecordingConfigurations); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when calling onRecordingConfigChanged", e); + } + } + } + } + + private void updatePlayingApplications(List<AudioPlaybackConfiguration> configs) { + synchronized (mLock) { + ArraySet<Integer> playingAppUids = findPlayingAppUids(configs, mRunningAppUids); + if (mPlayingAppUids.equals(playingAppUids)) { + return; + } + mPlayingAppUids = playingAppUids; + } + + // Updated rerouted apps, even if the app is already playing. It originally should be done + // when onRunningAppsChanged() is called, but we don't want to interrupt the audio + // streaming and cause the sound leak from phone when it's playing, so delay until here. + notifyAppsNeedingAudioRoutingChanged(); + } + + private void notifyAppsNeedingAudioRoutingChanged() { + if (mHandler.hasCallbacks(mUpdateAudioRoutingRunnable)) { + mHandler.removeCallbacks(mUpdateAudioRoutingRunnable); + } + + int[] runningUids; + synchronized (mLock) { + runningUids = new int[mRunningAppUids.size()]; + for (int i = 0; i < mRunningAppUids.size(); i++) { + runningUids[i] = mRunningAppUids.valueAt(i); + } + } + + synchronized (mCallbackLock) { + if (mCallback != null) { + try { + mCallback.onAppsNeedingAudioRoutingChanged(runningUids); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when calling updateReroutingApps", e); + } + } + } + } + + /** + * Finds uid of playing applications from the given running applications. + * + * @param configs a list of playback configs which get from {@link AudioManager} + */ + private static ArraySet<Integer> findPlayingAppUids(List<AudioPlaybackConfiguration> configs, + ArraySet<Integer> runningAppUids) { + ArraySet<Integer> playingAppUids = new ArraySet<>(); + for (AudioPlaybackConfiguration config : configs) { + if (runningAppUids.contains(config.getClientUid()) + && config.getPlayerState() == PLAYER_STATE_STARTED) { + playingAppUids.add(config.getClientUid()); + } + } + return playingAppUids; + } + + /** Finds a list of {@link AudioPlaybackConfiguration} for the given running applications. */ + private static List<AudioPlaybackConfiguration> findPlaybackConfigurations( + List<AudioPlaybackConfiguration> configs, + ArraySet<Integer> runningAppUids) { + List<AudioPlaybackConfiguration> runningConfigs = new ArrayList<>(); + for (AudioPlaybackConfiguration config : configs) { + if (runningAppUids.contains(config.getClientUid())) { + runningConfigs.add(config); + } + } + return runningConfigs; + } + + /** Finds a list of {@link AudioRecordingConfiguration} for the given running applications. */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + private static List<AudioRecordingConfiguration> findRecordingConfigurations( + List<AudioRecordingConfiguration> configs, ArraySet<Integer> runningAppUids) { + List<AudioRecordingConfiguration> runningConfigs = new ArrayList<>(); + for (AudioRecordingConfiguration config : configs) { + if (runningAppUids.contains(config.getClientUid())) { + runningConfigs.add(config); + } + } + return runningConfigs; + } + + @VisibleForTesting + boolean hasPendingRunnable() { + return mHandler.hasCallbacks(mUpdateAudioRoutingRunnable); + } + + @VisibleForTesting + void addPlayingAppsForTesting(int appUid) { + synchronized (mLock) { + mPlayingAppUids.add(appUid); + } + } +} diff --git a/services/core/java/com/android/server/AccessibilityManagerInternal.java b/services/core/java/com/android/server/AccessibilityManagerInternal.java new file mode 100644 index 000000000000..28f6db1c800b --- /dev/null +++ b/services/core/java/com/android/server/AccessibilityManagerInternal.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.annotation.NonNull; +import android.os.IBinder; +import android.util.ArraySet; +import android.util.SparseArray; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; + +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodSession; + +/** + * Accessibility manager local system service interface. + */ +public abstract class AccessibilityManagerInternal { + /** Enable or disable the sessions. */ + public abstract void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, + boolean enabled); + + /** Unbind input for all accessibility services which require ime capabilities. */ + public abstract void unbindInput(); + + /** Bind input for all accessibility services which require ime capabilities. */ + public abstract void bindInput(InputBinding binding); + + /** + * Request input session from all accessibility services which require ime capabilities and + * whose id is not in the ignoreSet. + */ + public abstract void createImeSession(ArraySet<Integer> ignoreSet); + + /** Start input for all accessibility services which require ime capabilities. */ + public abstract void startInput(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting); + + private static final AccessibilityManagerInternal NOP = new AccessibilityManagerInternal() { + @Override + public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, + boolean enabled) { + } + + @Override + public void unbindInput() { + } + + @Override + public void bindInput(InputBinding binding) { + } + + @Override + public void createImeSession(ArraySet<Integer> ignoreSet) { + } + + @Override + public void startInput(IBinder startInputToken, IInputContext inputContext, + EditorInfo editorInfo, boolean restarting) { + } + }; + + /** + * @return Global instance if exists. Otherwise, a fallback no-op instance. + */ + @NonNull + public static AccessibilityManagerInternal get() { + final AccessibilityManagerInternal instance = + LocalServices.getService(AccessibilityManagerInternal.class); + return instance != null ? instance : NOP; + } +} diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 39ac5effe6fa..b59cd4c0f212 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.OBSERVE_NETWORK_POLICY; import static android.Manifest.permission.SHUTDOWN; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -30,6 +31,7 @@ import static android.net.INetd.FIREWALL_DENYLIST; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -206,6 +208,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { */ @GuardedBy("mRulesLock") private SparseIntArray mUidFirewallRestrictedRules = new SparseIntArray(); + /** + * Contains the per-UID firewall rules that are used when Low Power Standby is enabled. + */ + @GuardedBy("mRulesLock") + private SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray(); /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mRulesLock") final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray(); @@ -506,12 +513,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub { syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable "); syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave "); syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted "); + syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby "); final int[] chains = { FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, - FIREWALL_CHAIN_RESTRICTED + FIREWALL_CHAIN_RESTRICTED, + FIREWALL_CHAIN_LOW_POWER_STANDBY }; for (int chain : chains) { @@ -1438,6 +1447,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return FIREWALL_CHAIN_NAME_POWERSAVE; case FIREWALL_CHAIN_RESTRICTED: return FIREWALL_CHAIN_NAME_RESTRICTED; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; default: throw new IllegalArgumentException("Bad child chain: " + chain); } @@ -1453,6 +1464,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_RESTRICTED: return FIREWALL_ALLOWLIST; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return FIREWALL_ALLOWLIST; default: return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST; } @@ -1571,6 +1584,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return mUidFirewallPowerSaveRules; case FIREWALL_CHAIN_RESTRICTED: return mUidFirewallRestrictedRules; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return mUidFirewallLowPowerStandbyRules; case FIREWALL_CHAIN_NONE: return mUidFirewallRules; default: @@ -1626,6 +1641,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { pw.println(getFirewallChainState(FIREWALL_CHAIN_RESTRICTED)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_RESTRICTED, mUidFirewallRestrictedRules); + + pw.print("UID firewall low power standby chain enabled: "); + pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY, + mUidFirewallLowPowerStandbyRules); } pw.print("Firewall enabled: "); pw.println(mFirewallEnabled); @@ -1749,6 +1769,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of restricted mode"); return true; } + if (getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY) + && mUidFirewallLowPowerStandbyRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby"); + return true; + } if (mUidRejectOnMetered.get(uid)) { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data" + " in the background"); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 178b66691f2f..42bd9269e103 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -4674,7 +4674,7 @@ class StorageManagerService extends IStorageManager.Stub private int getMountModeInternal(int uid, String packageName) { try { // Get some easy cases out of the way first - if (Process.isIsolated(uid)) { + if (Process.isIsolated(uid) || Process.isSupplemental(uid)) { return StorageManager.MOUNT_MODE_EXTERNAL_NONE; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 9353dd832bba..092172a15861 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2721,8 +2721,8 @@ public final class ActiveServices { int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, final IServiceConnection connection, int flags, - String instanceName, boolean isSupplementalProcessService, String callingPackage, - final int userId) + String instanceName, boolean isSupplementalProcessService, int supplementedAppUid, + String callingPackage, final int userId) throws TransactionTooLargeException { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() @@ -2807,8 +2807,8 @@ public final class ActiveServices { final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; ServiceLookupResult res = retrieveServiceLocked(service, instanceName, - isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid, - userId, true, callerFg, isBindExternal, allowInstant); + isSupplementalProcessService, supplementedAppUid, resolvedType, callingPackage, + callingPid, callingUid, userId, true, callerFg, isBindExternal, allowInstant); if (res == null) { return 0; } @@ -3228,13 +3228,14 @@ public final class ActiveServices { int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant) { - return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage, + return retrieveServiceLocked(service, instanceName, false, 0, resolvedType, callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, allowInstant); } private ServiceLookupResult retrieveServiceLocked(Intent service, - String instanceName, boolean isSupplementalProcessService, String resolvedType, + String instanceName, boolean isSupplementalProcessService, int supplementedAppUid, + String resolvedType, String callingPackage, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant) { @@ -3411,9 +3412,11 @@ public final class ActiveServices { final Intent.FilterComparison filter = new Intent.FilterComparison(service.cloneFilter()); final ServiceRestarter res = new ServiceRestarter(); + String supplementalProcessName = isSupplementalProcessService ? instanceName + : null; r = new ServiceRecord(mAm, className, name, definingPackageName, definingUid, filter, sInfo, callingFromFg, res, - isSupplementalProcessService); + supplementalProcessName, supplementedAppUid); res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); @@ -4187,8 +4190,16 @@ public final class ActiveServices { if (app == null && !permissionsReviewRequired && !packageFrozen) { // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service // was initiated from a notification tap or not. - if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, - hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated)) == null) { + if (r.supplemental) { + final int uid = Process.toSupplementalUid(r.supplementedAppUid); + app = mAm.startSupplementalProcessLocked(procName, r.appInfo, true, intentFlags, + hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, uid); + r.isolationHostProc = app; + } else { + app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, + hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated); + } + if (app == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java index d9ee7d974864..535340b4c9c1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerLocal.java +++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java @@ -17,10 +17,12 @@ package com.android.server.am; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.os.TransactionTooLargeException; +import android.os.RemoteException; /** * Interface for in-process calls into @@ -63,22 +65,28 @@ public interface ActivityManagerLocal { void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs); /** - * Starts a supplemental process service and binds to it. You can through the arguments here - * have the system bring up multiple concurrent processes hosting their own instance of that - * service. The <var>userAppUid</var> you provide here identifies the different instances - each - * unique uid is attributed to a supplemental process. + * Binds to a supplemental process service, creating it if needed. You can through the arguments + * here have the system bring up multiple concurrent processes hosting their own instance of + * that service. The {@code processName} you provide here identifies the different instances. * * @param service Identifies the supplemental process service to connect to. The Intent must * specify an explicit component name. This value cannot be null. * @param conn Receives information as the service is started and stopped. * This must be a valid ServiceConnection object; it must not be null. * @param userAppUid Uid of the app for which the supplemental process needs to be spawned. + * @param processName Unique identifier for the service instance. Each unique name here will + * result in a different service instance being created. Identifiers must only contain + * ASCII letters, digits, underscores, and periods. + * @param flags Operation options provided by Context class for the binding. * @return {@code true} if the system is in the process of bringing up a * service that your client has permission to bind to; {@code false} * if the system couldn't find the service or if your client doesn't * have permission to bind to it. + * @throws RemoteException If the service could not be brought up. + * @see Context#bindService(Intent, ServiceConnection, int) */ - boolean startAndBindSupplementalProcessService(@NonNull Intent service, - @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException; - + @SuppressLint("RethrowRemoteException") + boolean bindSupplementalProcessService(@NonNull Intent service, @NonNull ServiceConnection conn, + int userAppUid, @NonNull String processName, @Context.BindServiceFlags int flags) + throws RemoteException; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5cc163ddfe9d..2da6fb4a8560 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -458,7 +458,7 @@ public class ActivityManagerService extends IActivityManager.Stub * broadcasts */ private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT = - SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false); + SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", true); static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM; static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; @@ -1892,6 +1892,8 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord app = mProcessList.newProcessRecordLocked(info, info.processName, false, 0, + false, + 0, new HostingRecord("system")); app.setPersistent(true); app.setPid(MY_PID); @@ -2780,18 +2782,32 @@ public class ActivityManagerService extends IActivityManager.Stub false /* knownToBeDead */, 0 /* intentFlags */, sNullHostingRecord /* hostingRecord */, ZYGOTE_POLICY_FLAG_EMPTY, true /* allowWhileBooting */, true /* isolated */, - uid, abiOverride, entryPoint, entryPointArgs, crashHandler); + uid, false /* supplemental */, 0 /* supplementalUid */, + abiOverride, entryPoint, entryPointArgs, crashHandler); return proc != null; } } @GuardedBy("this") + final ProcessRecord startSupplementalProcessLocked(String processName, + ApplicationInfo info, boolean knownToBeDead, int intentFlags, + HostingRecord hostingRecord, int zygotePolicyFlags, int supplementalUid) { + return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags, + hostingRecord, zygotePolicyFlags, false /* allowWhileBooting */, + false /* isolated */, 0 /* isolatedUid */, + true /* supplemental */, supplementalUid, + null /* ABI override */, null /* entryPoint */, + null /* entryPointArgs */, null /* crashHandler */); + } + + @GuardedBy("this") final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated) { return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags, hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */, + false /* supplemental */, 0 /* supplementalUid */, null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */, null /* crashHandler */); } @@ -6521,6 +6537,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (app == null) { app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0, + false, 0, new HostingRecord("added application", customProcess != null ? customProcess : info.processName)); updateLruProcessLocked(app, false, null); @@ -12346,12 +12363,13 @@ public class ActivityManagerService extends IActivityManager.Stub String resolvedType, IServiceConnection connection, int flags, String instanceName, String callingPackage, int userId) throws TransactionTooLargeException { return bindServiceInstance(caller, token, service, resolvedType, connection, flags, - instanceName, false, callingPackage, userId); + instanceName, false, 0, callingPackage, userId); } private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String instanceName, - boolean isSupplementalProcessService, String callingPackage, int userId) + boolean isSupplementalProcessService, int supplementedAppUid, String callingPackage, + int userId) throws TransactionTooLargeException { enforceNotIsolatedCaller("bindService"); @@ -12382,7 +12400,8 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, - flags, instanceName, isSupplementalProcessService, callingPackage, userId); + flags, instanceName, isSupplementalProcessService, supplementedAppUid, + callingPackage, userId); } } @@ -15951,14 +15970,17 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean startAndBindSupplementalProcessService(Intent service, - ServiceConnection conn, int userAppUid) throws TransactionTooLargeException { + public boolean bindSupplementalProcessService(Intent service, ServiceConnection conn, + int userAppUid, String processName, int flags) throws RemoteException { if (service == null) { throw new IllegalArgumentException("intent is null"); } if (conn == null) { throw new IllegalArgumentException("connection is null"); } + if (processName == null) { + throw new IllegalArgumentException("processName is null"); + } if (service.getComponent() == null) { throw new IllegalArgumentException("service must specify explicit component"); } @@ -15967,15 +15989,14 @@ public class ActivityManagerService extends IActivityManager.Stub } Handler handler = mContext.getMainThreadHandler(); - int flags = Context.BIND_AUTO_CREATE; final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags); service.prepareToLeaveProcess(mContext); return ActivityManagerService.this.bindServiceInstance( - mContext.getIApplicationThread(), mContext.getActivityToken(), service, - service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags, - Integer.toString(userAppUid), /*isSupplementalProcessService*/ true, - mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0; + mContext.getIApplicationThread(), mContext.getActivityToken(), service, + service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags, + processName, /*isSupplementalProcessService*/ true, userAppUid, + mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0; } @Override @@ -16119,23 +16140,6 @@ public class ActivityManagerService extends IActivityManager.Stub } /** - * Returns package name by pid. - */ - @Override - @Nullable - public String getPackageNameByPid(int pid) { - synchronized (mPidsSelfLocked) { - final ProcessRecord app = mPidsSelfLocked.get(pid); - - if (app != null && app.info != null) { - return app.info.packageName; - } - - return null; - } - } - - /** * Sets if the given pid has an overlay UI or not. * * @param pid The pid we are setting overlay UI for. diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 2f7249ea5d84..91822ac353ab 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -79,7 +79,6 @@ import android.util.StatsEvent; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; -import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BatteryUsageStatsProvider; import com.android.internal.os.BatteryUsageStatsStore; @@ -2488,7 +2487,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long ident = Binder.clearCallingIdentity(); try { - if (BatteryStatsHelper.checkWifiOnly(mContext)) { + if (BatteryStats.checkWifiOnly(mContext)) { flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY; } awaitCompletion(); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index bdfd02e9c25a..6c9187aaf337 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2491,6 +2491,7 @@ public class OomAdjuster { mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app); } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ + && !state.isRunningRemoteAnimation() // Because these can fire independent of oom_adj/procstate changes, we need // to throttle the actual dispatch of these requests in addition to the // processing of the requests. As a result, there is throttling both here diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 1ad0bcea711c..4539cc8e05a2 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2525,6 +2525,7 @@ public final class ProcessList { ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid, + boolean supplemental, int supplementalUid, String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) { long startTime = SystemClock.uptimeMillis(); ProcessRecord app; @@ -2618,7 +2619,8 @@ public final class ProcessList { if (app == null) { checkSlow(startTime, "startProcess: creating new process record"); - app = newProcessRecordLocked(info, processName, isolated, isolatedUid, hostingRecord); + app = newProcessRecordLocked(info, processName, isolated, isolatedUid, supplemental, + supplementalUid, hostingRecord); if (app == null) { Slog.w(TAG, "Failed making new process record for " + processName + "/" + info.uid + " isolated=" + isolated); @@ -3113,10 +3115,14 @@ public final class ProcessList { @GuardedBy("mService") ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, - boolean isolated, int isolatedUid, HostingRecord hostingRecord) { + boolean isolated, int isolatedUid, boolean supplemental, int supplementalUid, + HostingRecord hostingRecord) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; + if (supplemental) { + uid = supplementalUid; + } if (isolated) { if (isolatedUid == 0) { IsolatedUidRange uidRange = getOrCreateIsolatedUidRangeLocked(info, hostingRecord); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index da78e2d7504e..711c57669fd6 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -94,6 +94,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN final boolean exported; // from ServiceInfo.exported final Runnable restarter; // used to schedule retries of starting the service final long createRealTime; // when this service was created + final boolean supplemental; // whether this is a supplemental service + final int supplementedAppUid; // the app uid for which this supplemental service is running final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings = new ArrayMap<Intent.FilterComparison, IntentBindRecord>(); // All active bindings to the service. @@ -571,13 +573,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, Runnable restarter) { this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg, - restarter, false); + restarter, null, 0); } ServiceRecord(ActivityManagerService ams, ComponentName name, ComponentName instanceName, String definingPackageName, int definingUid, Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, - Runnable restarter, boolean isSupplementalProcessService) { + Runnable restarter, String supplementalProcessName, int supplementedAppUid) { this.ams = ams; this.name = name; this.instanceName = instanceName; @@ -588,9 +590,12 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN serviceInfo = sInfo; appInfo = sInfo.applicationInfo; packageName = sInfo.applicationInfo.packageName; - if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 - || isSupplementalProcessService) { + supplemental = supplementalProcessName != null; + this.supplementedAppUid = supplementedAppUid; + if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { processName = sInfo.processName + ":" + instanceName.getClassName(); + } else if (supplementalProcessName != null) { + processName = supplementalProcessName; } else { processName = sInfo.processName; } diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java index 6982513f8f2f..ab4d856c1197 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java @@ -20,22 +20,30 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; import android.app.AppGlobals; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; -import android.app.ambientcontext.AmbientContextEventResponse; import android.app.ambientcontext.AmbientContextManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.os.Binder; +import android.os.Bundle; import android.os.RemoteCallback; import android.os.RemoteException; -import android.service.ambientcontext.AmbientContextDetectionService; +import android.provider.Settings; +import android.service.ambientcontext.AmbientContextDetectionResult; +import android.service.ambientcontext.AmbientContextDetectionServiceStatus; +import android.text.TextUtils; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -44,7 +52,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.infra.AbstractPerUserSystemService; import java.io.PrintWriter; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Set; /** @@ -60,14 +69,12 @@ final class AmbientContextManagerPerUserService extends RemoteAmbientContextDetectionService mRemoteService; private ComponentName mComponentName; - private Context mContext; private Set<PendingIntent> mExistingPendingIntents; AmbientContextManagerPerUserService( @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) { super(master, lock, userId); - mContext = master.getContext(); - mExistingPendingIntents = new HashSet<>(); + mExistingPendingIntents = new ArraySet<>(); } void destroyLocked() { @@ -154,12 +161,13 @@ final class AmbientContextManagerPerUserService extends * package. A new registration from the same package will overwrite the previous registration. */ public void onRegisterObserver(AmbientContextEventRequest request, - PendingIntent pendingIntent) { + PendingIntent pendingIntent, RemoteCallback clientStatusCallback) { synchronized (mLock) { if (!setUpServiceIfNeeded()) { - Slog.w(TAG, "Service is not available at this moment."); - sendStatusUpdateIntent( - pendingIntent, AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE); + Slog.w(TAG, "Detection service is not available at this moment."); + sendStatusCallback( + clientStatusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } @@ -173,29 +181,58 @@ final class AmbientContextManagerPerUserService extends } // Register new package and add request to mExistingRequests - startDetection(request, callingPackage, createRemoteCallback()); + startDetection(request, callingPackage, createDetectionResultRemoteCallback(), + getServerStatusCallback(clientStatusCallback)); mExistingPendingIntents.add(pendingIntent); } } + /** + * Returns a RemoteCallback that handles the status from the detection service, and + * sends results to the client callback. + */ + private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) { + return new RemoteCallback(result -> { + AmbientContextDetectionServiceStatus serviceStatus = + (AmbientContextDetectionServiceStatus) result.get( + AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + String packageName = serviceStatus.getPackageName(); + Bundle bundle = new Bundle(); + bundle.putInt( + AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, + serviceStatus.getStatusCode()); + clientStatusCallback.sendResult(bundle); + int statusCode = serviceStatus.getStatusCode(); + Slog.i(TAG, "Got detection status of " + statusCode + + " for " + packageName); + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } + @VisibleForTesting void startDetection(AmbientContextEventRequest request, String callingPackage, - RemoteCallback callback) { + RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { Slog.d(TAG, "Requested detection of " + request.getEventTypes()); synchronized (mLock) { ensureRemoteServiceInitiated(); - mRemoteService.startDetection(request, callingPackage, callback); + mRemoteService.startDetection(request, callingPackage, detectionResultCallback, + statusCallback); } } /** * Sends an intent with a status code and empty events. */ - void sendStatusUpdateIntent(PendingIntent pendingIntent, int statusCode) { - AmbientContextEventResponse response = new AmbientContextEventResponse.Builder() - .setStatusCode(statusCode) - .build(); - sendResponseIntent(pendingIntent, response); + void sendStatusCallback(RemoteCallback statusCallback, int statusCode) { + Bundle bundle = new Bundle(); + bundle.putInt( + AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, + statusCode); + statusCallback.sendResult(bundle); } /** @@ -217,6 +254,113 @@ final class AmbientContextManagerPerUserService extends } } + public void onQueryServiceStatus(int[] eventTypes, String callingPackage, + RemoteCallback statusCallback) { + Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes) + + " for " + callingPackage); + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + sendStatusToCallback(statusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.queryServiceStatus( + eventTypes, + callingPackage, + getServerStatusCallback(statusCallback)); + } + } + + public void onStartConsentActivity(int[] eventTypes, String callingPackage) { + Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes) + + " for " + callingPackage); + + // Look up the recent task from the callingPackage + ActivityManager.RecentTaskInfo task; + ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks; + int userId = getUserId(); + try { + recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1, + /*flags*/ 0, userId); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to query recent tasks!"); + return; + } + + if ((recentTasks != null) || recentTasks.getList().isEmpty()) { + Slog.e(TAG, "Recent task list is empty!"); + return; + } + + task = recentTasks.getList().get(0); + if (!callingPackage.equals(task.topActivityInfo.packageName)) { + Slog.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName + + " doesn't match with camera client package name: " + callingPackage); + return; + } + + // Start activity as the same task from the callingPackage + Intent intent = new Intent(); + ComponentName consentComponent = getConsentComponent(); + if (consentComponent != null) { + Slog.e(TAG, "Starting consent activity for " + callingPackage); + Context context = getContext(); + String packageNameExtraKey = Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY, + userId); + String eventArrayExtraKey = Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY, + userId); + + // Create consent activity intent with the calling package name and requested events. + intent.setComponent(consentComponent); + if (packageNameExtraKey != null) { + intent.putExtra(packageNameExtraKey, callingPackage); + } + if (eventArrayExtraKey != null) { + intent.putExtra(eventArrayExtraKey, eventTypes); + } + + // Set parent to the calling app's task + ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchTaskId(task.taskId); + context.startActivity(intent, options.toBundle()); + } else { + Slog.e(TAG, "Consent component not found!"); + } + } + + /** + * Returns the consent activity component from config lookup. + */ + private ComponentName getConsentComponent() { + Context context = getContext(); + String consentComponent = Settings.Secure.getStringForUser( + context.getContentResolver(), + Settings.Secure.AMBIENT_CONTEXT_CONSENT_COMPONENT, + getUserId()); + if (TextUtils.isEmpty(consentComponent)) { + return null; + } + return ComponentName.unflattenFromString(consentComponent); + } + + /** + * Sends the result response with the specified status to the callback. + */ + void sendStatusToCallback(RemoteCallback callback, + @AmbientContextManager.StatusCode int status) { + Bundle bundle = new Bundle(); + bundle.putInt( + AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY, + status); + callback.sendResult(bundle); + } + @VisibleForTesting void stopDetection(String packageName) { Slog.d(TAG, "Stop detection for " + packageName); @@ -240,12 +384,13 @@ final class AmbientContextManagerPerUserService extends * Sends out the Intent to the client after the event is detected. * * @param pendingIntent Client's PendingIntent for callback - * @param response Response with status code and detection result + * @param result result from the detection service */ - private void sendResponseIntent(PendingIntent pendingIntent, - AmbientContextEventResponse response) { + private void sendDetectionResultIntent(PendingIntent pendingIntent, + AmbientContextDetectionResult result) { Intent intent = new Intent(); - intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE, response); + intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS, + new ArrayList(result.getEvents())); // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing // the PendingIntent as a backdoor to do this. BroadcastOptions options = BroadcastOptions.makeBasic(); @@ -254,38 +399,29 @@ final class AmbientContextManagerPerUserService extends pendingIntent.send(getContext(), 0, intent, null, null, null, options.toBundle()); Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": " - + response); + + result); } catch (PendingIntent.CanceledException e) { Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent); } } @NonNull - private RemoteCallback createRemoteCallback() { + private RemoteCallback createDetectionResultRemoteCallback() { return new RemoteCallback(result -> { - AmbientContextEventResponse response = (AmbientContextEventResponse) result.get( - AmbientContextDetectionService.RESPONSE_BUNDLE_KEY); + AmbientContextDetectionResult detectionResult = + (AmbientContextDetectionResult) result.get( + AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY); final long token = Binder.clearCallingIdentity(); try { - Set<PendingIntent> pendingIntentForFailedRequests = new HashSet<>(); for (PendingIntent pendingIntent : mExistingPendingIntents) { - // Send PendingIntent if a requesting package matches the response packages. - if (response.getPackageName().equals(pendingIntent.getCreatorPackage())) { - sendResponseIntent(pendingIntent, response); - - int statusCode = response.getStatusCode(); - if (statusCode != AmbientContextEventResponse.STATUS_SUCCESS) { - pendingIntentForFailedRequests.add(pendingIntent); - } - Slog.i(TAG, "Got response of " + response.getEvents() + " for " - + pendingIntent.getCreatorPackage() + ". Status: " + statusCode); + // Send PendingIntent to requesting packages + String creatorPackage = pendingIntent.getCreatorPackage(); + if (detectionResult.getPackageName().equals(creatorPackage)) { + sendDetectionResultIntent(pendingIntent, detectionResult); + Slog.i(TAG, "Got detection result of " + detectionResult.getEvents() + + " for " + creatorPackage); } } - - // Removes the failed requests from the existing requests. - for (PendingIntent pendingIntent : pendingIntentForFailedRequests) { - mExistingPendingIntents.remove(pendingIntent); - } } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java index 33905f2d1aa3..2cdf7e7c7133 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java @@ -24,8 +24,8 @@ import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; -import android.app.ambientcontext.AmbientContextEventResponse; -import android.app.ambientcontext.IAmbientContextEventObserver; +import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.IAmbientContextManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManagerInternal; @@ -76,7 +76,7 @@ public class AmbientContextManagerService extends @Override public void onStart() { - publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextEventObserver()); + publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextManagerInternal()); } @Override @@ -128,14 +128,16 @@ public class AmbientContextManagerService extends * specified events. Intended for use by shell command for testing. * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. */ - void startAmbientContextEvent(@UserIdInt int userId, AmbientContextEventRequest request, - String packageName, RemoteCallback callback) { + void startDetection(@UserIdInt int userId, AmbientContextEventRequest request, + String packageName, RemoteCallback detectionResultCallback, + RemoteCallback statusCallback) { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); synchronized (mLock) { final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); if (service != null) { - service.startDetection(request, packageName, callback); + service.startDetection(request, packageName, detectionResultCallback, + statusCallback); } else { Slog.i(TAG, "service not available for user_id: " + userId); } @@ -161,6 +163,25 @@ public class AmbientContextManagerService extends } /** + * Send request to the remote AmbientContextDetectionService impl to query the status of the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ + void queryServiceStatus(@UserIdInt int userId, String packageName, + int[] eventTypes, RemoteCallback callback) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.onQueryServiceStatus(eventTypes, packageName, callback); + } else { + Slog.i(TAG, "query service not available for user_id: " + userId); + } + } + } + + /** * Returns the AmbientContextManagerPerUserService component for this user. */ public ComponentName getComponentName(@UserIdInt int userId) { @@ -173,34 +194,65 @@ public class AmbientContextManagerService extends return null; } - private final class AmbientContextEventObserver extends IAmbientContextEventObserver.Stub { + private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub { final AmbientContextManagerPerUserService mService = getServiceForUserLocked( UserHandle.getCallingUserId()); @Override public void registerObserver( - AmbientContextEventRequest request, PendingIntent pendingIntent) { + AmbientContextEventRequest request, PendingIntent resultPendingIntent, + RemoteCallback statusCallback) { Objects.requireNonNull(request); - Objects.requireNonNull(pendingIntent); + Objects.requireNonNull(resultPendingIntent); + Objects.requireNonNull(statusCallback); mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + assertCalledByPackageOwner(resultPendingIntent.getCreatorPackage()); if (!mIsServiceEnabled) { Slog.w(TAG, "Service not available."); - mService.sendStatusUpdateIntent(pendingIntent, - AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE); + mService.sendStatusCallback(statusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); return; } - mService.onRegisterObserver(request, pendingIntent); + mService.onRegisterObserver(request, resultPendingIntent, statusCallback); } @Override public void unregisterObserver(String callingPackage) { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + assertCalledByPackageOwner(callingPackage); mService.onUnregisterObserver(callingPackage); } @Override + public void queryServiceStatus(int[] eventTypes, String callingPackage, + RemoteCallback statusCallback) { + Objects.requireNonNull(eventTypes); + Objects.requireNonNull(callingPackage); + Objects.requireNonNull(statusCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + assertCalledByPackageOwner(callingPackage); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Detection service not available."); + mService.sendStatusToCallback(statusCallback, + AmbientContextManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + mService.onQueryServiceStatus(eventTypes, callingPackage, + statusCallback); + } + + @Override + public void startConsentActivity(int[] eventTypes, String callingPackage) { + Objects.requireNonNull(eventTypes); + Objects.requireNonNull(callingPackage); + assertCalledByPackageOwner(callingPackage); + mService.onStartConsentActivity(eventTypes, callingPackage); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { return; diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java index b5cd985fa097..010bf1b4cfb8 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java @@ -21,12 +21,12 @@ import static java.lang.System.out; import android.annotation.NonNull; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; -import android.app.ambientcontext.AmbientContextEventResponse; import android.content.ComponentName; import android.os.Binder; import android.os.RemoteCallback; import android.os.ShellCommand; -import android.service.ambientcontext.AmbientContextDetectionService; +import android.service.ambientcontext.AmbientContextDetectionResult; +import android.service.ambientcontext.AmbientContextDetectionServiceStatus; import java.io.PrintWriter; @@ -35,6 +35,12 @@ import java.io.PrintWriter; */ final class AmbientContextShellCommand extends ShellCommand { + private static final AmbientContextEventRequest REQUEST = + new AmbientContextEventRequest.Builder() + .addEventType(AmbientContextEvent.EVENT_COUGH) + .addEventType(AmbientContextEvent.EVENT_SNORE) + .build(); + @NonNull private final AmbientContextManagerService mService; @@ -44,22 +50,43 @@ final class AmbientContextShellCommand extends ShellCommand { /** Callbacks for AmbientContextEventService results used internally for testing. */ static class TestableCallbackInternal { - private AmbientContextEventResponse mLastResponse; + private AmbientContextDetectionResult mLastResult; + private AmbientContextDetectionServiceStatus mLastStatus; + + public AmbientContextDetectionResult getLastResult() { + return mLastResult; + } - public AmbientContextEventResponse getLastResponse() { - return mLastResponse; + public AmbientContextDetectionServiceStatus getLastStatus() { + return mLastStatus; } @NonNull - private RemoteCallback createRemoteCallback() { + private RemoteCallback createRemoteDetectionResultCallback() { return new RemoteCallback(result -> { - AmbientContextEventResponse response = - (AmbientContextEventResponse) result.get( - AmbientContextDetectionService.RESPONSE_BUNDLE_KEY); + AmbientContextDetectionResult detectionResult = + (AmbientContextDetectionResult) result.get( + AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY); final long token = Binder.clearCallingIdentity(); try { - mLastResponse = response; - out.println("Response available: " + response); + mLastResult = detectionResult; + out.println("Detection result available: " + detectionResult); + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } + + @NonNull + private RemoteCallback createRemoteStatusCallback() { + return new RemoteCallback(result -> { + AmbientContextDetectionServiceStatus status = + (AmbientContextDetectionServiceStatus) result.get( + AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + mLastStatus = status; + out.println("Status available: " + status); } finally { Binder.restoreCallingIdentity(token); } @@ -83,6 +110,8 @@ final class AmbientContextShellCommand extends ShellCommand { return runStopDetection(); case "get-last-status-code": return getLastStatusCode(); + case "query-service-status": + return runQueryServiceStatus(); case "get-bound-package": return getBoundPackageName(); case "set-temporary-service": @@ -95,13 +124,9 @@ final class AmbientContextShellCommand extends ShellCommand { private int runStartDetection() { final int userId = Integer.parseInt(getNextArgRequired()); final String packageName = getNextArgRequired(); - AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() - .addEventType(AmbientContextEvent.EVENT_COUGH) - .addEventType(AmbientContextEvent.EVENT_SNORE) - .build(); - - mService.startAmbientContextEvent(userId, request, packageName, - sTestableCallbackInternal.createRemoteCallback()); + mService.startDetection(userId, REQUEST, packageName, + sTestableCallbackInternal.createRemoteDetectionResultCallback(), + sTestableCallbackInternal.createRemoteStatusCallback()); return 0; } @@ -112,8 +137,20 @@ final class AmbientContextShellCommand extends ShellCommand { return 0; } + private int runQueryServiceStatus() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + int[] types = new int[] { + AmbientContextEvent.EVENT_COUGH, + AmbientContextEvent.EVENT_SNORE}; + mService.queryServiceStatus(userId, packageName, types, + sTestableCallbackInternal.createRemoteStatusCallback()); + return 0; + } + private int getLastStatusCode() { - AmbientContextEventResponse lastResponse = sTestableCallbackInternal.getLastResponse(); + AmbientContextDetectionServiceStatus lastResponse = + sTestableCallbackInternal.getLastStatus(); if (lastResponse == null) { return -1; } @@ -130,6 +167,7 @@ final class AmbientContextShellCommand extends ShellCommand { pw.println(" start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection."); pw.println(" stop-detection USER_ID: Stops AmbientContextEvent detection."); pw.println(" get-last-status-code: Prints the latest request status code."); + pw.println(" query-event-status USER_ID PACKAGE_NAME: Prints the event status code."); pw.println(" get-bound-package USER_ID:" + " Print the bound package that implements the service."); pw.println(" set-temporary-service USER_ID [COMPONENT_NAME DURATION]"); diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java index 5cc29b3c34c0..f42080d550b7 100644 --- a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java +++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java @@ -20,6 +20,7 @@ import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; import android.content.ComponentName; import android.content.Context; @@ -53,13 +54,15 @@ final class RemoteAmbientContextDetectionService * * @param request The request with events to detect, and optional detection options. * @param packageName The app package that requested the detection - * @param callback callback for detection results + * @param detectionResultCallback callback for detection results + * @param statusCallback callback for service status */ public void startDetection( @NonNull AmbientContextEventRequest request, String packageName, - RemoteCallback callback) { + RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { Slog.i(TAG, "Start detection for " + request.getEventTypes()); - post(service -> service.startDetection(request, packageName, callback)); + post(service -> service.startDetection(request, packageName, detectionResultCallback, + statusCallback)); } /** @@ -71,4 +74,15 @@ final class RemoteAmbientContextDetectionService Slog.i(TAG, "Stop detection for " + packageName); post(service -> service.stopDetection(packageName)); } + + /** + * Asks the implementation to return the event status for the package. + */ + public void queryServiceStatus( + @AmbientContextEvent.EventCode int[] eventTypes, + String packageName, + RemoteCallback callback) { + Slog.i(TAG, "Query status for " + packageName); + post(service -> service.queryServiceStatus(eventTypes, packageName, callback)); + } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 40fda4cbec5e..cebcc64f7d5e 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -4549,6 +4549,26 @@ public class AppOpsService extends IAppOpsService.Stub { return new PackageVerificationResult(null, /* isAttributionTagValid */ true); } + if (Process.isSupplemental(uid)) { + // Supplemental processes run in their own UID range, but their associated + // UID for checks should always be the UID of the supplemental package. + // TODO: We will need to modify the callers of this function instead, so + // modifications and checks against the app ops state are done with the + // correct UID. + try { + final PackageManager pm = mContext.getPackageManager(); + final String supplementalPackageName = pm.getSupplementalProcessPackageName(); + if (Objects.equals(packageName, supplementalPackageName)) { + int supplementalAppId = pm.getPackageUid(supplementalPackageName, + PackageManager.PackageInfoFlags.of(0)); + uid = UserHandle.getUid(UserHandle.getUserId(uid), supplementalAppId); + } + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't happen for the supplemental package + e.printStackTrace(); + } + } + // Do not check if uid/packageName/attributionTag is already known. synchronized (this) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 05955c3cab44..cedc7dbd727a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -22,6 +22,7 @@ import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; @@ -147,6 +148,7 @@ import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; +import android.util.ArraySet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; @@ -329,6 +331,9 @@ public class AudioService extends IAudioService.Stub private static final int MSG_ROUTING_UPDATED = 41; private static final int MSG_INIT_HEADTRACKING_SENSORS = 42; private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43; + private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44; + private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45; + private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), @@ -341,6 +346,9 @@ public class AudioService extends IAudioService.Stub // retry delay in case of failure to indicate system ready to AudioFlinger private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000; + // List of empty UIDs used to reset the active assistant list + private static final int[] NO_ACTIVE_ASSISTANT_SERVICE_UIDS = new int[0]; + /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ @@ -756,10 +764,15 @@ public class AudioService extends IAudioService.Stub private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; private long mLoweredFromNormalToVibrateTime; - // Uid of the active hotword detection service to check if caller is the one or not. - @GuardedBy("mHotwordDetectionServiceUidLock") - private int mHotwordDetectionServiceUid = android.os.Process.INVALID_UID; - private final Object mHotwordDetectionServiceUidLock = new Object(); + // Array of Uids of valid assistant services to check if caller is one of them + @GuardedBy("mSettingsLock") + private final ArraySet<Integer> mAssistantUids = new ArraySet<>(); + @GuardedBy("mSettingsLock") + private int mPrimaryAssistantUid = INVALID_UID; + + // Array of Uids of valid active assistant service to check if caller is one of them + @GuardedBy("mSettingsLock") + private int[] mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; // Array of Uids of valid accessibility services to check if caller is one of them private final Object mAccessibilityServiceUidsLock = new Object(); @@ -787,9 +800,6 @@ public class AudioService extends IAudioService.Stub private boolean mHomeSoundEffectEnabled; @GuardedBy("mSettingsLock") - private int mAssistantUid; - - @GuardedBy("mSettingsLock") private int mCurrentImeUid; private final Object mSupportedSystemUsagesLock = new Object(); @@ -1395,12 +1405,10 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); - updateAssistantUId(true); AudioSystem.setRttEnabled(mRttEnabled); + updateAssistantServicesUidsLocked(); } - synchronized (mHotwordDetectionServiceUidLock) { - AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid); - } + synchronized (mAccessibilityServiceUidsLock) { AudioSystem.setA11yServicesUids(mAccessibilityServiceUids); } @@ -1478,6 +1486,68 @@ public class AudioService extends IAudioService.Stub updateVibratorInfos(); } + private void onRemoveAssistantServiceUids(int[] uids) { + synchronized (mSettingsLock) { + removeAssistantServiceUidsLocked(uids); + } + } + + @GuardedBy("mSettingsLock") + private void removeAssistantServiceUidsLocked(int[] uids) { + boolean changed = false; + for (int index = 0; index < uids.length; index++) { + if (!mAssistantUids.remove(uids[index])) { + Slog.e(TAG, TextUtils.formatSimple( + "Cannot remove assistant service, uid(%d) not present", uids[index])); + continue; + } + changed = true; + } + if (changed) { + updateAssistantServicesUidsLocked(); + } + } + + private void onAddAssistantServiceUids(int[] uids) { + synchronized (mSettingsLock) { + addAssistantServiceUidsLocked(uids); + } + } + + @GuardedBy("mSettingsLock") + private void addAssistantServiceUidsLocked(int[] uids) { + boolean changed = false; + for (int index = 0; index < uids.length; index++) { + if (uids[index] == INVALID_UID) { + continue; + } + if (!mAssistantUids.add(uids[index])) { + Slog.e(TAG, TextUtils.formatSimple( + "Cannot add assistant service, uid(%d) already present", + uids[index])); + continue; + } + changed = true; + } + if (changed) { + updateAssistantServicesUidsLocked(); + } + } + + @GuardedBy("mSettingsLock") + private void updateAssistantServicesUidsLocked() { + int[] assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray(); + AudioSystem.setAssistantServicesUids(assistantUids); + } + + private void updateActiveAssistantServiceUids() { + int [] activeAssistantServiceUids; + synchronized (mSettingsLock) { + activeAssistantServiceUids = mActiveAssistantServiceUids; + } + AudioSystem.setActiveAssistantServicesUids(activeAssistantServiceUids); + } + private void onReinitVolumes(@NonNull String caller) { final int numStreamTypes = AudioSystem.getNumStreamTypes(); // keep track of any error during stream volume initialization @@ -2243,8 +2313,7 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mSettingsLock") private void updateAssistantUId(boolean forceUpdate) { - int assistantUid = 0; - + int assistantUid = INVALID_UID; // Consider assistants in the following order of priority: // 1) apk in assistant role // 2) voice interaction service @@ -2288,10 +2357,10 @@ public class AudioService extends IAudioService.Stub } } } - - if (assistantUid != mAssistantUid || forceUpdate) { - AudioSystem.setAssistantUid(assistantUid); - mAssistantUid = assistantUid; + if ((mPrimaryAssistantUid != assistantUid) || forceUpdate) { + mAssistantUids.remove(mPrimaryAssistantUid); + mPrimaryAssistantUid = assistantUid; + addAssistantServiceUidsLocked(new int[]{mPrimaryAssistantUid}); } } @@ -2342,6 +2411,7 @@ public class AudioService extends IAudioService.Stub sendEncodedSurroundMode(cr, "readPersistedSettings"); sendEnabledSurroundFormats(cr, true); updateAssistantUId(true); + resetActiveAssistantUidsLocked(); AudioSystem.setRttEnabled(mRttEnabled); } @@ -2367,6 +2437,12 @@ public class AudioService extends IAudioService.Stub mVolumeController.loadSettings(cr); } + @GuardedBy("mSettingsLock") + private void resetActiveAssistantUidsLocked() { + mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; + updateActiveAssistantServiceUids(); + } + private void readUserRestrictions() { if (!mSystemServer.isPrivileged()) { return; @@ -7843,6 +7919,17 @@ public class AudioService extends IAudioService.Stub case MSG_PERSIST_SPATIAL_AUDIO_ENABLED: onPersistSpatialAudioEnabled(msg.arg1 == 1); break; + + case MSG_ADD_ASSISTANT_SERVICE_UID: + onAddAssistantServiceUids(new int[]{msg.arg1}); + break; + + case MSG_REMOVE_ASSISTANT_SERVICE_UID: + onRemoveAssistantServiceUids(new int[]{msg.arg1}); + break; + case MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID: + updateActiveAssistantServiceUids(); + break; } } } @@ -9351,7 +9438,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand); pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); pw.print(" mMcc="); pw.println(mMcc); - pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); + pw.print(" mCameraSoundForced="); pw.println(isCameraSoundForced()); pw.print(" mHasVibrator="); pw.println(mHasVibrator); pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported); @@ -9371,9 +9458,9 @@ public class AudioService extends IAudioService.Stub + " FromRestrictions=" + mMicMuteFromRestrictions + " FromApi=" + mMicMuteFromApi + " from system=" + mMicMuteFromSystemCached); - pw.print("\n mAssistantUid="); pw.println(mAssistantUid); pw.print(" mCurrentImeUid="); pw.println(mCurrentImeUid); dumpAccessibilityServiceUids(pw); + dumpAssistantServicesUids(pw); dumpAudioPolicies(pw); mDynPolicyLogger.dump(pw); @@ -9416,6 +9503,19 @@ public class AudioService extends IAudioService.Stub } } + private void dumpAssistantServicesUids(PrintWriter pw) { + synchronized (mSettingsLock) { + if (mAssistantUids.size() > 0) { + pw.println(" Assistant service UIDs:"); + for (int uid : mAssistantUids) { + pw.println(" - " + uid); + } + } else { + pw.println(" No Assistant service Uids."); + } + } + } + private void dumpAccessibilityServiceUids(PrintWriter pw) { synchronized (mSupportedSystemUsagesLock) { if (mAccessibilityServiceUids != null && mAccessibilityServiceUids.length > 0) { @@ -9714,13 +9814,40 @@ public class AudioService extends IAudioService.Stub } @Override - public void setHotwordDetectionServiceUid(int uid) { - synchronized (mHotwordDetectionServiceUidLock) { - if (mHotwordDetectionServiceUid != uid) { - mHotwordDetectionServiceUid = uid; - AudioSystem.setHotwordDetectionServiceUid(mHotwordDetectionServiceUid); + public void addAssistantServiceUid(int uid) { + sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, + uid, 0, null, 0); + } + + @Override + public void removeAssistantServiceUid(int uid) { + sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, + uid, 0, null, 0); + } + + @Override + public void setActiveAssistantServicesUids(IntArray activeUids) { + synchronized (mSettingsLock) { + if (activeUids.size() == 0) { + mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; + } else { + boolean changed = (mActiveAssistantServiceUids == null) + || (mActiveAssistantServiceUids.length != activeUids.size()); + if (!changed) { + for (int i = 0; i < mActiveAssistantServiceUids.length; i++) { + if (activeUids.get(i) != mActiveAssistantServiceUids[i]) { + changed = true; + break; + } + } + } + if (changed) { + mActiveAssistantServiceUids = activeUids.toArray(); + } } } + sendMsg(mAudioHandler, MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID, SENDMSG_REPLACE, + 0, 0, null, 0); } @Override @@ -11017,6 +11144,59 @@ public class AudioService extends IAudioService.Stub return delayMillis; } + /** @see AudioManager#addAssistantServicesUids(int []) */ + @Override + public void addAssistantServicesUids(int [] assistantUids) { + enforceModifyAudioRoutingPermission(); + Objects.requireNonNull(assistantUids); + + synchronized (mSettingsLock) { + addAssistantServiceUidsLocked(assistantUids); + } + } + + /** @see AudioManager#removeAssistantServicesUids(int []) */ + @Override + public void removeAssistantServicesUids(int [] assistantUids) { + enforceModifyAudioRoutingPermission(); + Objects.requireNonNull(assistantUids); + synchronized (mSettingsLock) { + removeAssistantServiceUidsLocked(assistantUids); + } + } + + /** @see AudioManager#getAssistantServicesUids() */ + @Override + public int[] getAssistantServicesUids() { + enforceModifyAudioRoutingPermission(); + int [] assistantUids; + synchronized (mSettingsLock) { + assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray(); + } + return assistantUids; + } + + /** @see AudioManager#setActiveAssistantServiceUids(int []) */ + @Override + public void setActiveAssistantServiceUids(int [] activeAssistantUids) { + enforceModifyAudioRoutingPermission(); + synchronized (mSettingsLock) { + mActiveAssistantServiceUids = activeAssistantUids; + } + updateActiveAssistantServiceUids(); + } + + /** @see AudioManager#getActiveAssistantServiceUids() */ + @Override + public int[] getActiveAssistantServiceUids() { + enforceModifyAudioRoutingPermission(); + int [] activeAssistantUids; + synchronized (mSettingsLock) { + activeAssistantUids = mActiveAssistantServiceUids.clone(); + } + return activeAssistantUids; + } + UUID getDeviceSensorUuid(AudioDeviceAttributes device) { return mDeviceBroker.getDeviceSensorUuid(device); } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index f572261c09e5..a70b4701bb7b 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -387,14 +387,6 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { } /** - * Same as {@link AudioSystem#setHotwordDetectionServiceUid(int)} - * Communicate UID of current HotwordDetectionService to audio policy service. - */ - public int setHotwordDetectionServiceUid(int uid) { - return AudioSystem.setHotwordDetectionServiceUid(uid); - } - - /** * Same as {@link AudioSystem#setCurrentImeUid(int)} * Communicate UID of current InputMethodService to audio policy service. */ diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java index c5e266f87149..c86a8cb2c39d 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -44,7 +44,7 @@ public interface BiometricContext { @Nullable Integer getBiometricPromptSessionId(); /** If the display is in AOD. */ - boolean isAoD(); + boolean isAod(); /** * Subscribe to context changes. diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java index 70acaff05e30..9d2fde72ea2e 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -120,7 +120,7 @@ class BiometricContextProvider implements BiometricContext { @Override public OperationContext updateContext(@NonNull OperationContext operationContext, boolean isCryptoOperation) { - operationContext.isAoD = isAoD(); + operationContext.isAod = isAod(); operationContext.isCrypto = isCryptoOperation; setFirstSessionId(operationContext); return operationContext; @@ -160,7 +160,7 @@ class BiometricContextProvider implements BiometricContext { } @Override - public boolean isAoD() { + public boolean isAod() { return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); } @@ -177,7 +177,7 @@ class BiometricContextProvider implements BiometricContext { private void notifySubscribers() { mSubscribers.forEach((context, consumer) -> { - context.isAoD = isAoD(); + context.isAod = isAod(); consumer.accept(context); }); } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index 8965227a1bb4..d6ca8a68145e 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -56,7 +56,7 @@ public class BiometricFrameworkStatsLogger { -1 /* sensorId */, operationContext.id, sessionType(operationContext.reason), - operationContext.isAoD); + operationContext.isAod); } /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ @@ -77,7 +77,7 @@ public class BiometricFrameworkStatsLogger { ambientLightLux, operationContext.id, sessionType(operationContext.reason), - operationContext.isAoD); + operationContext.isAod); } /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ @@ -109,7 +109,7 @@ public class BiometricFrameworkStatsLogger { -1 /* sensorId */, operationContext.id, sessionType(operationContext.reason), - operationContext.isAoD); + operationContext.isAod); } /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index d26a78008529..653776b3ca65 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -229,7 +229,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> context.y = y; context.minor = minor; context.major = major; - context.isAoD = getBiometricContext().isAoD(); + context.isAod = getBiometricContext().isAod(); session.getSession().onPointerDownWithContext(context); } else { session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index e21d901b135d..c92d599d68e6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -213,7 +213,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps context.y = y; context.minor = minor; context.major = major; - context.isAoD = getBiometricContext().isAoD(); + context.isAod = getBiometricContext().isAod(); session.getSession().onPointerDownWithContext(context); } else { session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java index 452c972ca784..67511533de66 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java @@ -204,8 +204,8 @@ public class TestHal extends IFingerprint.Stub { @Override public void onPointerDownWithContext(PointerContext context) { - onPointerDown( - context.pointerId, context.x, context.y, context.minor, context.major); + onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor, + context.major); } @Override diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 35e3db7832f1..a311ba1825b9 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -418,7 +418,7 @@ final class DisplayDeviceInfo { || !Objects.equals(deviceProductInfo, other.deviceProductInfo) || ownerUid != other.ownerUid || !Objects.equals(ownerPackageName, other.ownerPackageName) - || !Objects.equals(frameRateOverrides, other.frameRateOverrides) + || !Arrays.equals(frameRateOverrides, other.frameRateOverrides) || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum) || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum) || !BrightnessSynchronizer.floatEquals(brightnessDefault, diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 7f1482e6dde1..f50011020694 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -78,6 +78,7 @@ import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.hardware.display.WifiDisplayStatus; +import android.hardware.graphics.common.DisplayDecorationSupport; import android.hardware.input.InputManagerInternal; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionManager; @@ -1860,10 +1861,10 @@ public final class DisplayManagerService extends SystemService { return mDisplayModeDirector.getModeSwitchingType(); } - private boolean getDisplayDecorationSupportInternal(int displayId) { + private DisplayDecorationSupport getDisplayDecorationSupportInternal(int displayId) { final IBinder displayToken = getDisplayToken(displayId); if (null == displayToken) { - return false; + return null; } return SurfaceControl.getDisplayDecorationSupport(displayToken); } @@ -3550,7 +3551,7 @@ public final class DisplayManagerService extends SystemService { } @Override // Binder call - public boolean getDisplayDecorationSupport(int displayId) { + public DisplayDecorationSupport getDisplayDecorationSupport(int displayId) { final long token = Binder.clearCallingIdentity(); try { return getDisplayDecorationSupportInternal(displayId); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index a31c2314bd1f..c02e725e4785 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -997,7 +997,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { public boolean updateFrameRateOverridesLocked( DisplayEventReceiver.FrameRateOverride[] overrides) { - if (overrides.equals(mFrameRateOverrides)) { + if (Arrays.equals(overrides, mFrameRateOverrides)) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index b81478285a62..b2f500a59ba9 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -422,7 +422,7 @@ final class InputMethodBindingController { addFreshWindowToken(); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, - null, null, mCurId, mCurSeq, false); + null, null, null, mCurId, mCurSeq, false); } Slog.w(InputMethodManagerService.TAG, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 80c83e97256e..29dcdfaa1bba 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -25,6 +25,7 @@ import android.view.inputmethod.InputMethodInfo; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InlineSuggestionsRequestInfo; import com.android.server.LocalServices; @@ -149,6 +150,24 @@ public abstract class InputMethodManagerInternal { public abstract void updateImeWindowStatus(boolean disableImeIcon); /** + * Callback when the IInputMethodSession from the accessibility service with the specified + * accessibilityConnectionId is created. + * + * @param accessibilityConnectionId The connection id of the accessibility service. + * @param session The session passed back from the accessibility service. + */ + public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId, + IInputMethodSession session); + + /** + * Unbind the accessibility service with the specified accessibilityConnectionId from current + * client. + * + * @param accessibilityConnectionId The connection id of the accessibility service. + */ + public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId); + + /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ private static final InputMethodManagerInternal NOP = @@ -211,6 +230,15 @@ public abstract class InputMethodManagerInternal { @Override public void updateImeWindowStatus(boolean disableImeIcon) { } + + @Override + public void onSessionForAccessibilityCreated(int accessibilityConnectionId, + IInputMethodSession session) { + } + + @Override + public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) { + } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index ba15a047af4a..7068ed13376f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -94,6 +94,7 @@ import android.media.AudioManagerInternal; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.DeadObjectException; import android.os.Debug; import android.os.Handler; import android.os.IBinder; @@ -120,6 +121,7 @@ import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.IWindowManager; import android.view.InputChannel; @@ -171,6 +173,7 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.IInputSessionCallback; import com.android.internal.view.InlineSuggestionsRequestInfo; +import com.android.server.AccessibilityManagerInternal; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -228,7 +231,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private static final int MSG_START_HANDWRITING = 1100; private static final int MSG_UNBIND_CLIENT = 3000; + private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 3001; private static final int MSG_BIND_CLIENT = 3010; + private static final int MSG_BIND_ACCESSIBILITY_SERVICE = 3011; private static final int MSG_SET_ACTIVE = 3020; private static final int MSG_SET_INTERACTIVE = 3030; private static final int MSG_REPORT_FULLSCREEN_MODE = 3045; @@ -351,6 +356,33 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + /** + * Record session state for an accessibility service. + */ + private static class AccessibilitySessionState { + final ClientState mClient; + // Id of the accessibility service. + final int mId; + + public IInputMethodSession mSession; + + @Override + public String toString() { + return "AccessibilitySessionState{uid " + mClient.uid + " pid " + mClient.pid + + " id " + Integer.toHexString(mId) + + " session " + Integer.toHexString( + System.identityHashCode(mSession)) + + "}"; + } + + AccessibilitySessionState(ClientState client, int id, + IInputMethodSession session) { + mClient = client; + mId = id; + mSession = session; + } + } + private static final class ClientDeathRecipient implements IBinder.DeathRecipient { private final InputMethodManagerService mImms; private final IInputMethodClient mClient; @@ -376,7 +408,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ClientDeathRecipient clientDeathRecipient; boolean sessionRequested; + boolean mSessionRequestedForAccessibility; SessionState curSession; + SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>(); @Override public String toString() { @@ -637,10 +671,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub boolean mBoundToMethod; /** + * Have we called bindInput() for accessibility services? + */ + boolean mBoundToAccessibility; + + /** * Currently enabled session. */ @GuardedBy("ImfLock.class") SessionState mEnabledSession; + SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>(); /** * True if the device is currently interactive with user. The value is true initially. @@ -2188,6 +2228,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (cs != null) { client.asBinder().unlinkToDeath(cs.clientDeathRecipient, 0); clearClientSessionLocked(cs); + clearClientSessionForAccessibilityLocked(cs); if (mCurClient == cs) { hideCurrentInputLocked( mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); @@ -2195,9 +2236,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mBoundToMethod = false; IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { + // When we unbind input, we are unbinding the client, so we always + // unbind ime and a11y together. curMethod.unbindInput(); + AccessibilityManagerInternal.get().unbindInput(); } } + mBoundToAccessibility = false; mCurClient = null; } if (mCurFocusedWindowClient == cs) { @@ -2216,6 +2261,24 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @NonNull + private Message obtainMessageOOO(int what, Object arg1, Object arg2, Object arg3) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg1; + args.arg2 = arg2; + args.arg3 = arg3; + return mHandler.obtainMessage(what, 0, 0, args); + } + + @NonNull + private Message obtainMessageIIOO(int what, int arg1, int arg2, + Object arg3, Object arg4) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg3; + args.arg2 = arg4; + return mHandler.obtainMessage(what, arg1, arg2, args); + } + + @NonNull private Message obtainMessageIIIO(int what, int argi1, int argi2, int argi3, Object arg1) { final SomeArgs args = SomeArgs.obtain(); args.arg1 = arg1; @@ -2252,13 +2315,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub curMethod.unbindInput(); } } + mBoundToAccessibility = false; + // Since we set active false to current client and set mCurClient to null, let's unbind + // all accessibility too. That means, when input method get disconnected (including + // switching ime), we also unbind accessibility scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */, false /* reportToImeController */); executeOrSendMessage(mCurClient.client, mHandler.obtainMessage( MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason, mCurClient.client)); mCurClient.sessionRequested = false; + mCurClient.mSessionRequestedForAccessibility = false; mCurClient = null; mMenuController.hideInputMethodMenuLocked(); @@ -2340,11 +2408,63 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); + final SparseArray<IInputMethodSession> accessibilityInputMethodSessions = + createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, - session.session, (session.channel != null ? session.channel.dup() : null), + session.session, accessibilityInputMethodSessions, + (session.channel != null ? session.channel.dup() : null), curId, getSequenceNumberLocked(), suppressesSpellChecker); } + @GuardedBy("ImfLock.class") + @Nullable + InputBindResult attachNewAccessibilityLocked(@StartInputReason int startInputReason, + boolean initial, int id) { + if (!mBoundToAccessibility) { + AccessibilityManagerInternal.get().bindInput(mCurClient.binding); + mBoundToAccessibility = true; + } + + // TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to + // record accessibility services uid. + + final AccessibilitySessionState accessibilitySession = + mCurClient.mAccessibilitySessions.get(id); + // We don't start input when session for a11y is created. We start input when + // input method start input, a11y manager service is always on. + if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) { + final Binder startInputToken = new Binder(); + setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions); + AccessibilityManagerInternal.get().startInput(startInputToken, mCurInputContext, + mCurAttribute, !initial /* restarting */); + } + + if (accessibilitySession != null) { + final SessionState session = mCurClient.curSession; + IInputMethodSession imeSession = session == null ? null : session.session; + final SparseArray<IInputMethodSession> accessibilityInputMethodSessions = + createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); + return new InputBindResult( + InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, + imeSession, accessibilityInputMethodSessions, null, + getCurIdLocked(), getSequenceNumberLocked(), false); + } + return null; + } + + private SparseArray<IInputMethodSession> createAccessibilityInputMethodSessions( + SparseArray<AccessibilitySessionState> accessibilitySessions) { + final SparseArray<IInputMethodSession> accessibilityInputMethodSessions = + new SparseArray<>(); + if (accessibilitySessions != null) { + for (int i = 0; i < accessibilitySessions.size(); i++) { + accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i), + accessibilitySessions.valueAt(i).mSession); + } + } + return accessibilityInputMethodSessions; + } + /** * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the * selected InputMethod to the given focused IME client. @@ -2370,7 +2490,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // party code. return new InputBindResult( InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, - null, null, selectedMethodId, getSequenceNumberLocked(), false); + null, null, null, selectedMethodId, getSequenceNumberLocked(), false); } if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid, @@ -2422,6 +2542,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (cs.curSession != null) { // Fast case: if we are already connected to the input method, // then just return it. + // This doesn't mean a11y sessions are there. When a11y service is + // enabled while this client is switched out, this client doesn't have the session. + // A11yManagerService will only request missing sessions (will not request existing + // sessions again). Note when an a11y service is disabled, it will clear its + // session from all clients, so we don't need to worry about disabled a11y services. + cs.mSessionRequestedForAccessibility = false; + requestClientSessionForAccessibilityLocked(cs); + // we can always attach to accessibility because AccessibilityManagerService is + // always on. + attachNewAccessibilityLocked(startInputReason, + (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, -1); return attachNewInputLocked(startInputReason, (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); } @@ -2464,9 +2595,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Return to client, and we will get back with it when // we have had a session made for it. requestClientSessionLocked(cs); + requestClientSessionForAccessibilityLocked(cs); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, - null, null, getCurIdLocked(), getSequenceNumberLocked(), false); + null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false); } else { long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked(); if (bindingDuration < TIME_TO_RECONNECT) { @@ -2479,7 +2611,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // to see if we can get back in touch with the service. return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, - null, null, getCurIdLocked(), getSequenceNumberLocked(), false); + null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodIdLocked(), bindingDuration, 0); @@ -2565,6 +2697,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub method, session, channel); InputBindResult res = attachNewInputLocked( StartInputReason.SESSION_CREATED_BY_IME, true); + attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, + true, -1); if (res.method != null) { executeOrSendMessage(mCurClient.client, obtainMessageOO( MSG_BIND_CLIENT, mCurClient.client, res)); @@ -2602,7 +2736,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub void reRequestCurrentClientSessionLocked() { if (mCurClient != null) { clearClientSessionLocked(mCurClient); + clearClientSessionForAccessibilityLocked(mCurClient); requestClientSessionLocked(mCurClient); + requestClientSessionForAccessibilityLocked(mCurClient); } } @@ -2646,6 +2782,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + void requestClientSessionForAccessibilityLocked(ClientState cs) { + if (!cs.mSessionRequestedForAccessibility) { + if (DEBUG) Slog.v(TAG, "Creating new accessibility sessions for client " + cs); + cs.mSessionRequestedForAccessibility = true; + ArraySet<Integer> ignoreSet = new ArraySet<>(); + for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) { + ignoreSet.add(cs.mAccessibilitySessions.keyAt(i)); + } + AccessibilityManagerInternal.get().createImeSession(ignoreSet); + } + } + + @GuardedBy("ImfLock.class") void clearClientSessionLocked(ClientState cs) { finishSessionLocked(cs.curSession); cs.curSession = null; @@ -2653,6 +2802,24 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + void clearClientSessionForAccessibilityLocked(ClientState cs) { + for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) { + finishSessionForAccessibilityLocked(cs.mAccessibilitySessions.valueAt(i)); + } + cs.mAccessibilitySessions.clear(); + cs.mSessionRequestedForAccessibility = false; + } + + @GuardedBy("ImfLock.class") + void clearClientSessionForAccessibilityLocked(ClientState cs, int id) { + AccessibilitySessionState session = cs.mAccessibilitySessions.get(id); + if (session != null) { + finishSessionForAccessibilityLocked(session); + cs.mAccessibilitySessions.remove(id); + } + } + + @GuardedBy("ImfLock.class") private void finishSessionLocked(SessionState sessionState) { if (sessionState != null) { if (sessionState.session != null) { @@ -2672,15 +2839,34 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + private void finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState) { + if (sessionState != null) { + if (sessionState.mSession != null) { + try { + sessionState.mSession.finishSession(); + } catch (RemoteException e) { + Slog.w(TAG, "Session failed to close due to remote exception", e); + } + sessionState.mSession = null; + } + } + } + + @GuardedBy("ImfLock.class") void clearClientSessionsLocked() { if (getCurMethodLocked() != null) { final int numClients = mClients.size(); for (int i = 0; i < numClients; ++i) { clearClientSessionLocked(mClients.valueAt(i)); + clearClientSessionForAccessibilityLocked(mClients.valueAt(i)); } finishSessionLocked(mEnabledSession); + for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { + finishSessionForAccessibilityLocked(mEnabledAccessibilitySessions.valueAt(i)); + } mEnabledSession = null; + mEnabledAccessibilitySessions.clear(); scheduleNotifyImeUidToAudioService(Process.INVALID_UID); } hideStatusBarIconLocked(); @@ -3455,7 +3641,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, - null, null, null, -1, false); + null, null, null, null, -1, false); } mCurFocusedWindow = windowToken; @@ -4250,6 +4436,41 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + @GuardedBy("ImfLock.class") + void setEnabledSessionForAccessibilityLocked( + SparseArray<AccessibilitySessionState> accessibilitySessions) { + // mEnabledAccessibilitySessions could the same object as accessibilitySessions. + SparseArray<IInputMethodSession> disabledSessions = new SparseArray<>(); + for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { + if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) { + AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i); + if (sessionState != null) { + disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i), + sessionState.mSession); + } + } + } + if (disabledSessions.size() > 0) { + AccessibilityManagerInternal.get().setImeSessionEnabled(disabledSessions, + false); + } + SparseArray<IInputMethodSession> enabledSessions = new SparseArray<>(); + for (int i = 0; i < accessibilitySessions.size(); i++) { + if (!mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) { + AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i); + if (sessionState != null) { + enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession); + } + } + } + if (enabledSessions.size() > 0) { + AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions, + true); + } + mEnabledAccessibilitySessions = accessibilitySessions; + } + + @SuppressWarnings("unchecked") @UiThread @Override public boolean handleMessage(Message msg) { @@ -4319,13 +4540,34 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // --------------------------------------------------------- - case MSG_UNBIND_CLIENT: + case MSG_UNBIND_CLIENT: { try { - ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2); + // This unbinds all accessibility services too. + ((IInputMethodClient) msg.obj).onUnbindMethod(msg.arg1, msg.arg2); } catch (RemoteException e) { // There is nothing interesting about the last client dying. + if (!(e instanceof DeadObjectException)) { + Slog.w(TAG, "RemoteException when unbinding input method service or" + + "accessibility services"); + } } return true; + } + case MSG_UNBIND_ACCESSIBILITY_SERVICE: { + args = (SomeArgs) msg.obj; + IInputMethodClient client = (IInputMethodClient) args.arg1; + int id = (int) args.arg2; + try { + client.onUnbindAccessibilityService(msg.arg1, id); + } catch (RemoteException e) { + // There is nothing interesting about the last client dying. + if (!(e instanceof DeadObjectException)) { + Slog.w(TAG, "RemoteException when unbinding accessibility services"); + } + } + args.recycle(); + return true; + } case MSG_BIND_CLIENT: { args = (SomeArgs)msg.obj; IInputMethodClient client = (IInputMethodClient)args.arg1; @@ -4344,6 +4586,25 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub args.recycle(); return true; } + case MSG_BIND_ACCESSIBILITY_SERVICE: { + args = (SomeArgs) msg.obj; + IInputMethodClient client = (IInputMethodClient) args.arg1; + InputBindResult res = (InputBindResult) args.arg2; + int id = (int) args.arg3; + try { + client.onBindAccessibilityService(res, id); + } catch (RemoteException e) { + Slog.w(TAG, "Client died receiving input method " + args.arg2); + } finally { + // Dispose the channel if the accessibility service is not local to this process + // because the remote proxy will get its own copy when unparceled. + if (res.channel != null && Binder.isProxy(client)) { + res.channel.dispose(); + } + } + args.recycle(); + return true; + } case MSG_SET_ACTIVE: { args = (SomeArgs) msg.obj; final ClientState clientState = (ClientState) args.arg1; @@ -5025,6 +5286,59 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0) .sendToTarget(); } + + @Override + public void onSessionForAccessibilityCreated(int accessibilityConnectionId, + IInputMethodSession session) { + synchronized (ImfLock.class) { + if (mCurClient != null) { + clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); + mCurClient.mAccessibilitySessions.put(accessibilityConnectionId, + new AccessibilitySessionState(mCurClient, accessibilityConnectionId, + session)); + InputBindResult res = attachNewAccessibilityLocked( + StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY, true, + accessibilityConnectionId); + executeOrSendMessage(mCurClient.client, obtainMessageOOO( + MSG_BIND_ACCESSIBILITY_SERVICE, mCurClient.client, res, + accessibilityConnectionId)); + } + } + } + + @Override + public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) { + synchronized (ImfLock.class) { + if (mCurClient != null) { + if (DEBUG) { + Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client=" + + mCurClient.client.asBinder()); + } + // A11yManagerService unbinds the disabled accessibility service. We don't need + // to do it here. + @UnbindReason int unbindClientReason = + UnbindReason.ACCESSIBILITY_SERVICE_DISABLED; + executeOrSendMessage(mCurClient.client, obtainMessageIIOO( + MSG_UNBIND_ACCESSIBILITY_SERVICE, getSequenceNumberLocked(), + unbindClientReason, mCurClient.client, accessibilityConnectionId)); + } + // We only have sessions when we bound to an input method. Remove this session + // from all clients. + if (getCurMethodLocked() != null) { + final int numClients = mClients.size(); + for (int i = 0; i < numClients; ++i) { + clearClientSessionForAccessibilityLocked(mClients.valueAt(i), + accessibilityConnectionId); + } + AccessibilitySessionState session = mEnabledAccessibilitySessions.get( + accessibilityConnectionId); + if (session != null) { + finishSessionForAccessibilityLocked(session); + mEnabledAccessibilitySessions.remove(accessibilityConnectionId); + } + } + } + } } @BinderThread @@ -5184,6 +5498,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println(" client=" + ci.client); p.println(" inputContext=" + ci.inputContext); p.println(" sessionRequested=" + ci.sessionRequested); + p.println(" sessionRequestedForAccessibility=" + + ci.mSessionRequestedForAccessibility); p.println(" curSession=" + ci.curSession); } p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 3abe5e2e7e8a..4d3e4389783b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -148,6 +148,8 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationTo import com.android.server.locksettings.SyntheticPasswordManager.TokenType; import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.pm.UserManagerInternal; +import com.android.server.utils.Watchable; +import com.android.server.utils.Watcher; import com.android.server.wm.WindowManagerInternal; import libcore.util.HexEncoding; @@ -230,6 +232,7 @@ public class LockSettingsService extends ILockSettings.Stub { protected final Handler mHandler; @VisibleForTesting protected final LockSettingsStorage mStorage; + private final Watcher mStorageWatcher; private final LockSettingsStrongAuth mStrongAuth; private final SynchronizedStrongAuthTracker mStrongAuthTracker; private final BiometricDeferredQueue mBiometricDeferredQueue; @@ -573,6 +576,12 @@ public class LockSettingsService extends ILockSettings.Stub { } } + private class StorageWatcher extends Watcher { + public void onChange(Watchable what) { + LockSettingsService.this.onChange(); + } + } + public LockSettingsService(Context context) { this(new Injector(context)); } @@ -614,6 +623,16 @@ public class LockSettingsService extends ILockSettings.Stub { mStorage); LocalServices.addService(LockSettingsInternal.class, new LocalService()); + + mStorageWatcher = new StorageWatcher(); + mStorage.registerObserver(mStorageWatcher); + } + + /** + * Invalidate caches if the storage has changed. + */ + private void onChange() { + LockPatternUtils.invalidateCredentialTypeCache(); } /** @@ -1278,6 +1297,11 @@ public class LockSettingsService extends ILockSettings.Stub { DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId); } + /** + * This API is cached; whenever the result would change, + * {@link com.android.internal.widget.LockPatternUtils#invalidateCredentialTypeCache} + * must be called. + */ @Override public int getCredentialType(int userId) { checkPasswordHavePermission(userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index f69a3bd9d7ce..f9db5cf998bb 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -47,6 +47,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.CredentialType; import com.android.server.LocalServices; import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.utils.WatchableImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -66,7 +67,7 @@ import java.util.Map; /** * Storage for the lock settings service. */ -class LockSettingsStorage { +class LockSettingsStorage extends WatchableImpl { private static final String TAG = "LockSettingsStorage"; private static final String TABLE = "locksettings"; @@ -173,7 +174,7 @@ class LockSettingsStorage { } finally { db.endTransaction(); } - + dispatchChange(this); } @VisibleForTesting @@ -221,7 +222,7 @@ class LockSettingsStorage { } finally { db.endTransaction(); } - + dispatchChange(this); } public void prefetchUser(int userId) { @@ -412,6 +413,7 @@ class LockSettingsStorage { } } mCache.putFile(name, hash); + dispatchChange(this); } } @@ -423,6 +425,7 @@ class LockSettingsStorage { file.delete(); mCache.putFile(name, null); } + dispatchChange(this); } } @@ -500,6 +503,7 @@ class LockSettingsStorage { Slog.w(TAG, "Failed to zeroize " + path, e); } finally { file.delete(); + dispatchChange(this); } mCache.putFile(path, null); } @@ -587,6 +591,7 @@ class LockSettingsStorage { } finally { db.endTransaction(); } + dispatchChange(this); } private void deleteFilesAndRemoveCache(String... names) { @@ -595,6 +600,7 @@ class LockSettingsStorage { if (file.exists()) { file.delete(); mCache.putFile(name, null); + dispatchChange(this); } } } @@ -675,6 +681,7 @@ class LockSettingsStorage { } persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes( persistentType, userId, qualityForUi, payload)); + dispatchChange(this); } public PersistentData readPersistentDataBlock() { diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java deleted file mode 100644 index 6b442a6a395e..000000000000 --- a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2022 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.logcat; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentSender; -import android.os.Bundle; -import android.os.ServiceManager; -import android.os.logcat.ILogcatManagerService; -import android.util.Slog; -import android.view.View; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.internal.app.AlertActivity; -import com.android.internal.app.AlertController; - - -/** - * This dialog is shown to the user before an activity in a harmful app is launched. - * - * See {@code PackageManager.setLogcatAppInfo} for more info. - */ -public class LogAccessConfirmationActivity extends AlertActivity implements - DialogInterface.OnClickListener { - private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName(); - - private String mPackageName; - private IntentSender mTarget; - private final ILogcatManagerService mLogcatManagerService = - ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat")); - - private int mUid; - private int mGid; - private int mPid; - private int mFd; - - private static final String EXTRA_UID = "uid"; - private static final String EXTRA_GID = "gid"; - private static final String EXTRA_PID = "pid"; - private static final String EXTRA_FD = "fd"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final Intent intent = getIntent(); - mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); - mUid = intent.getIntExtra("uid", 0); - mGid = intent.getIntExtra("gid", 0); - mPid = intent.getIntExtra("pid", 0); - mFd = intent.getIntExtra("fd", 0); - - final AlertController.AlertParams p = mAlertParams; - p.mTitle = getString(R.string.log_access_confirmation_title); - p.mView = createView(); - - p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow); - p.mPositiveButtonListener = this; - p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny); - p.mNegativeButtonListener = this; - - mAlert.installContent(mAlertParams); - } - - private View createView() { - final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog, - null /*root*/); - ((TextView) view.findViewById(R.id.app_name_text)) - .setText(mPackageName); - ((TextView) view.findViewById(R.id.message)) - .setText(getIntent().getExtras().getString("body")); - return view; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - try { - mLogcatManagerService.approve(mUid, mGid, mPid, mFd); - } catch (Throwable t) { - Slog.e(TAG, "Could not start the LogcatManagerService.", t); - } - finish(); - break; - case DialogInterface.BUTTON_NEGATIVE: - try { - mLogcatManagerService.decline(mUid, mGid, mPid, mFd); - } catch (Throwable t) { - Slog.e(TAG, "Could not start the LogcatManagerService.", t); - } - finish(); - break; - } - } - - /** - * Create the Intent for a LogAccessConfirmationActivity. - */ - public static Intent createIntent(Context context, String targetPackageName, - IntentSender target, int uid, int gid, int pid, int fd) { - final Intent intent = new Intent(); - intent.setClass(context, LogAccessConfirmationActivity.class); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); - intent.putExtra(EXTRA_UID, uid); - intent.putExtra(EXTRA_GID, gid); - intent.putExtra(EXTRA_PID, pid); - intent.putExtra(EXTRA_FD, fd); - - return intent; - } - -} diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index 78322967ec15..ff6372aec3bd 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -16,36 +16,20 @@ package com.android.server.logcat; -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.app.ActivityManager.RunningAppProcessInfo; -import android.app.ActivityManagerInternal; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.os.ILogd; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; import android.os.logcat.ILogcatManagerService; import android.util.Slog; -import com.android.internal.R; -import com.android.internal.notification.SystemNotificationChannels; -import com.android.internal.util.ArrayUtils; -import com.android.server.LocalServices; import com.android.server.SystemService; -import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - * Service responsible for managing the access to Logcat. + * Service responsible for manage the access to Logcat. */ public final class LogcatManagerService extends SystemService { @@ -54,43 +38,6 @@ public final class LogcatManagerService extends SystemService { private final BinderService mBinderService; private final ExecutorService mThreadExecutor; private ILogd mLogdService; - private NotificationManager mNotificationManager; - private @NonNull ActivityManager mActivityManager; - private ActivityManagerInternal mActivityManagerInternal; - private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2; - private static int sUidImportanceListenerCount = 0; - private static final int AID_APP_UID = 10000; - - // TODO This allowlist is just a temporary workaround for the tests: - // FrameworksServicesTests - // PlatformRuleTests - // After adapting the test suites, the allowlist will be removed in - // the upcoming bug fix patches. - private static final String[] ALLOWABLE_TESTING_PACKAGES = { - "android.platform.test.rule.tests", - "com.android.frameworks.servicestests" - }; - - // TODO Same as the above ALLOWABLE_TESTING_PACKAGES. - private boolean isAllowableTestingPackage(int uid) { - PackageManager pm = mContext.getPackageManager(); - - String[] packageNames = pm.getPackagesForUid(uid); - - if (ArrayUtils.isEmpty(packageNames)) { - return false; - } - - for (String name : packageNames) { - Slog.e(TAG, "isAllowableTestingPackage: " + name); - - if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) { - return true; - } - } - - return false; - }; private final class BinderService extends ILogcatManagerService.Stub { @Override @@ -104,197 +51,6 @@ public final class LogcatManagerService extends SystemService { // the logd data access is finished. mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false)); } - - @Override - public void approve(int uid, int gid, int pid, int fd) { - try { - getLogdService().approve(uid, gid, pid, fd); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - @Override - public void decline(int uid, int gid, int pid, int fd) { - try { - getLogdService().decline(uid, gid, pid, fd); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } - - private ILogd getLogdService() { - synchronized (LogcatManagerService.this) { - if (mLogdService == null) { - LogcatManagerService.this.addLogdService(); - } - return mLogdService; - } - } - - private String getBodyString(Context context, String callingPackage, int uid) { - PackageManager pm = context.getPackageManager(); - try { - return context.getString( - com.android.internal.R.string.log_access_confirmation_body, - pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO, - UserHandle.getUserId(uid)).loadLabel(pm)); - } catch (NameNotFoundException e) { - // App name is unknown. - return null; - } - } - - private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid, - int fd) { - - final ActivityManagerInternal activityManagerInternal = - LocalServices.getService(ActivityManagerInternal.class); - - PackageManager pm = mContext.getPackageManager(); - String packageName = activityManagerInternal.getPackageNameByPid(pid); - if (packageName != null) { - String notificationBody = getBodyString(mContext, packageName, uid); - - final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, - packageName, null, uid, gid, pid, fd); - - if (notificationBody == null) { - // Decline the logd access if the nofitication body is unknown - Slog.e(TAG, "Unknown notification body, declining the logd access"); - declineLogdAccess(uid, gid, pid, fd); - return; - } - - // TODO Next version will replace notification with dialogue - // per UX guidance. - generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody, - mIntent); - return; - - } - - String[] packageNames = pm.getPackagesForUid(uid); - - if (ArrayUtils.isEmpty(packageNames)) { - // Decline the logd access if the app name is unknown - Slog.e(TAG, "Unknown calling package name, declining the logd access"); - declineLogdAccess(uid, gid, pid, fd); - return; - } - - String firstPackageName = packageNames[0]; - - if (firstPackageName == null || firstPackageName.length() == 0) { - // Decline the logd access if the package name from uid is unknown - Slog.e(TAG, "Unknown calling package name, declining the logd access"); - declineLogdAccess(uid, gid, pid, fd); - return; - } - - String notificationBody = getBodyString(mContext, firstPackageName, uid); - - final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, - firstPackageName, null, uid, gid, pid, fd); - - if (notificationBody == null) { - Slog.e(TAG, "Unknown notification body, declining the logd access"); - declineLogdAccess(uid, gid, pid, fd); - return; - } - - // TODO Next version will replace notification with dialogue - // per UX guidance. - generateNotificationWithBodyContent(notificationId, clientInfo, - notificationBody, mIntent); - } - - private void declineLogdAccess(int uid, int gid, int pid, int fd) { - try { - getLogdService().decline(uid, gid, pid, fd); - } catch (RemoteException ex) { - Slog.e(TAG, "Fails to call remote functions ", ex); - } - } - - private void generateNotificationWithBodyContent(int notificationId, String clientInfo, - String notificationBody, Intent intent) { - final Notification.Builder notificationBuilder = new Notification.Builder( - mContext, - SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY); - intent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.setIdentifier(String.valueOf(notificationId) + clientInfo); - intent.putExtra("body", notificationBody); - - notificationBuilder - .setSmallIcon(R.drawable.ic_info) - .setContentTitle( - mContext.getString(R.string.log_access_confirmation_title)) - .setContentText(notificationBody) - .setContentIntent( - PendingIntent.getActivity(mContext, 0, intent, - PendingIntent.FLAG_IMMUTABLE)) - .setTicker(mContext.getString(R.string.log_access_confirmation_title)) - .setOnlyAlertOnce(true) - .setAutoCancel(true); - mNotificationManager.notify(notificationId, notificationBuilder.build()); - } - - /** - * A class which watches an uid for background access and notifies the logdMonitor when - * the package status becomes foreground (importance change) - */ - private class UidImportanceListener implements ActivityManager.OnUidImportanceListener { - private final int mExpectedUid; - private final int mExpectedGid; - private final int mExpectedPid; - private final int mExpectedFd; - private int mExpectedImportance; - private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE; - - UidImportanceListener(int uid, int gid, int pid, int fd, int importance) { - mExpectedUid = uid; - mExpectedGid = gid; - mExpectedPid = pid; - mExpectedFd = fd; - mExpectedImportance = importance; - } - - @Override - public void onUidImportance(int uid, int importance) { - if (uid == mExpectedUid) { - mCurrentImportance = importance; - - /** - * 1) If the process status changes to foreground, send a notification - * for user consent. - * 2) If the process status remains background, we decline logd access request. - **/ - if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { - String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd); - sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid, - mExpectedFd); - mActivityManager.removeOnUidImportanceListener(this); - - synchronized (LogcatManagerService.this) { - sUidImportanceListenerCount--; - } - } else { - try { - getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd); - } catch (RemoteException ex) { - Slog.e(TAG, "Fails to call remote functions ", ex); - } - } - } - } - } - - private static String getClientInfo(int uid, int gid, int pid, int fd) { - return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID=" - + Integer.toString(pid) + " FD=" + Integer.toString(fd); } private class LogdMonitor implements Runnable { @@ -318,7 +74,9 @@ public final class LogcatManagerService extends SystemService { } /** - * LogdMonitor generates a prompt for users. + * The current version grant the permission by default. + * And track the logd access. + * The next version will generate a prompt for users. * The users decide whether the logd access is allowed. */ @Override @@ -328,63 +86,10 @@ public final class LogcatManagerService extends SystemService { } if (mStart) { - - // TODO See the comments of ALLOWABLE_TESTING_PACKAGES. - if (isAllowableTestingPackage(mUid)) { - try { - getLogdService().approve(mUid, mGid, mPid, mFd); - } catch (RemoteException e) { - e.printStackTrace(); - } - return; - } - - // If the access request is coming from native apps, approve the logd access - // TODO: This is needed to make tooling to work. However, - // we intend to be stricter with respect to native processes in a follow-up CL - if (mUid < AID_APP_UID) { - try { - getLogdService().approve(mUid, mGid, mPid, mFd); - } catch (RemoteException e) { - e.printStackTrace(); - } - return; - } - - final int procState = LocalServices.getService(ActivityManagerInternal.class) - .getUidProcessState(mUid); - // If the process is foreground, send a notification for user consent - if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { - String clientInfo = getClientInfo(mUid, mGid, mPid, mFd); - sendNotification(0, clientInfo, mUid, mGid, mPid, mFd); - } else { - /** - * If the process is background, add a background process change listener and - * monitor if the process status changes. - * To avoid clients registering multiple listeners, we limit the number of - * maximum listeners to MAX_UID_IMPORTANCE_COUNT_LISTENER. - **/ - if (mActivityManager == null) { - return; - } - - synchronized (LogcatManagerService.this) { - if (sUidImportanceListenerCount < MAX_UID_IMPORTANCE_COUNT_LISTENER) { - // Trigger addOnUidImportanceListener when there is an update from - // the importance of the process - mActivityManager.addOnUidImportanceListener(new UidImportanceListener( - mUid, mGid, mPid, mFd, - RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE), - RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE); - sUidImportanceListenerCount++; - } else { - try { - getLogdService().decline(mUid, mGid, mPid, mFd); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } + try { + mLogdService.approve(mUid, mGid, mPid, mFd); + } catch (RemoteException ex) { + Slog.e(TAG, "Fails to call remote functions ", ex); } } } @@ -395,8 +100,6 @@ public final class LogcatManagerService extends SystemService { mContext = context; mBinderService = new BinderService(); mThreadExecutor = Executors.newCachedThreadPool(); - mActivityManager = context.getSystemService(ActivityManager.class); - mNotificationManager = mContext.getSystemService(NotificationManager.class); } @Override @@ -411,4 +114,5 @@ public final class LogcatManagerService extends SystemService { private void addLogdService() { mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd")); } + } diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index b66c4668f2a0..33ac6cdb269e 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -16,14 +16,16 @@ package com.android.server.net; import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; -import static android.net.INetd.FIREWALL_CHAIN_DOZABLE; -import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE; -import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED; -import static android.net.INetd.FIREWALL_CHAIN_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; import static android.net.INetd.FIREWALL_RULE_ALLOW; import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -328,6 +330,8 @@ public class NetworkPolicyLogger { return FIREWALL_CHAIN_NAME_POWERSAVE; case FIREWALL_CHAIN_RESTRICTED: return FIREWALL_CHAIN_NAME_RESTRICTED; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; default: return String.valueOf(chain); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index aafcc5831718..2717f0c4c8a4 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -83,6 +83,7 @@ import static android.service.notification.NotificationListenerService.NOTIFICAT import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; @@ -476,6 +477,14 @@ public class NotificationManagerService extends SystemService { @LoggingOnly private static final long RATE_LIMIT_TOASTS = 174840628L; + /** + * Whether listeners understand the more specific reason provided for notification + * cancellations from an assistant, rather than using the more general REASON_LISTENER_CANCEL. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) + private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L; + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -4388,6 +4397,13 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + // Cancellation reason. If the token comes from assistant, label the + // cancellation as coming from the assistant; default to LISTENER_CANCEL. + int reason = REASON_LISTENER_CANCEL; + if (mAssistants.isServiceTokenValidLocked(token)) { + reason = REASON_ASSISTANT_CANCEL; + } + if (keys != null) { final int N = keys.length; for (int i = 0; i < N; i++) { @@ -4400,7 +4416,7 @@ public class NotificationManagerService extends SystemService { } cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), - r.getSbn().getId(), userId); + r.getSbn().getId(), userId, reason); } } else { cancelAllLocked(callingUid, callingPid, info.userid, @@ -4494,12 +4510,13 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, - int callingUid, int callingPid, String pkg, String tag, int id, int userId) { + int callingUid, int callingPid, String pkg, String tag, int id, int userId, + int reason) { int mustNotHaveFlags = FLAG_ONGOING_EVENT; cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */, mustNotHaveFlags, true, - userId, REASON_LISTENER_CANCEL, info); + userId, reason, info); } /** @@ -4641,13 +4658,17 @@ public class NotificationManagerService extends SystemService { try { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + int cancelReason = REASON_LISTENER_CANCEL; + if (mAssistants.isServiceTokenValidLocked(token)) { + cancelReason = REASON_ASSISTANT_CANCEL; + } if (info.supportsProfiles()) { Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) " + "from " + info.component + " use cancelNotification(key) instead."); } else { cancelNotificationFromListenerLocked(info, callingUid, callingPid, - pkg, tag, id, info.userid); + pkg, tag, id, info.userid, cancelReason); } } } finally { @@ -11049,6 +11070,12 @@ public class NotificationManagerService extends SystemService { && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) { reason = REASON_CHANNEL_BANNED; } + // apps before T don't know about REASON_ASSISTANT, so replace it with the + // previously-used case, REASON_LISTENER_CANCEL + if (!CompatChanges.isChangeEnabled(NOTIFICATION_LOG_ASSISTANT_CANCEL, info.uid) + && reason == REASON_ASSISTANT_CANCEL) { + reason = REASON_LISTENER_CANCEL; + } listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason); } catch (RemoteException ex) { Slog.e(TAG, "unable to notify listener (removed): " + info, ex); diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index f3dc2dd55385..9a89efabc689 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CLICK; import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; @@ -180,7 +181,9 @@ public interface NotificationRecordLogger { + " shade.") NOTIFICATION_CANCEL_USER_SHADE(192), @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen") - NOTIFICATION_CANCEL_USER_LOCKSCREEN(193); + NOTIFICATION_CANCEL_USER_LOCKSCREEN(193), + @UiEvent(doc = "Notification was canceled due to an assistant adjustment update.") + NOTIFICATION_CANCEL_ASSISTANT(906); private final int mId; NotificationCancelledEvent(int id) { @@ -206,6 +209,9 @@ public interface NotificationRecordLogger { if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) { return NotificationCancelledEvent.values()[reason]; } + if (reason == REASON_ASSISTANT_CANCEL) { + return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT; + } if (NotificationManagerService.DBG) { throw new IllegalArgumentException("Unexpected cancel reason " + reason); } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index c942a4357900..78aa45a812e4 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -2134,6 +2134,9 @@ public class ComputerEngine implements Computer { private String[] getPackagesForUidInternal(int uid, int callingUid) { final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null; final int userId = UserHandle.getUserId(uid); + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp); } @@ -2369,6 +2372,10 @@ public class ComputerEngine implements Computer { } public final boolean isCallerSameApp(String packageName, int uid) { + if (Process.isSupplemental(uid)) { + return (packageName != null + && packageName.equals(mService.getSupplementalProcessPackageName())); + } AndroidPackage pkg = mPackages.get(packageName); return pkg != null && UserHandle.getAppId(uid) == pkg.getUid(); @@ -4288,6 +4295,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return null; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int callingUserId = UserHandle.getUserId(callingUid); final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); @@ -4320,7 +4330,11 @@ public class ComputerEngine implements Computer { final int callingUserId = UserHandle.getUserId(callingUid); final String[] names = new String[uids.length]; for (int i = uids.length - 1; i >= 0; i--) { - final int appId = UserHandle.getAppId(uids[i]); + int uid = uids[i]; + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } + final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); if (obj instanceof SharedUserSetting) { final SharedUserSetting sus = (SharedUserSetting) obj; @@ -4366,6 +4380,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return 0; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int callingUserId = UserHandle.getUserId(callingUid); final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); @@ -4391,6 +4408,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(callingUid) != null) { return 0; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int callingUserId = UserHandle.getUserId(callingUid); final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); @@ -4415,6 +4435,9 @@ public class ComputerEngine implements Computer { if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; } + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); final Object obj = mSettings.getSettingBase(appId); if (obj instanceof SharedUserSetting) { @@ -5539,6 +5562,9 @@ public class ComputerEngine implements Computer { @Override public int getUidTargetSdkVersion(int uid) { + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); final SettingBase settingBase = mSettings.getSettingBase(appId); if (settingBase instanceof SharedUserSetting) { @@ -5565,6 +5591,9 @@ public class ComputerEngine implements Computer { @Nullable @Override public ArrayMap<String, ProcessInfo> getProcessesForUid(int uid) { + if (Process.isSupplemental(uid)) { + uid = getSupplementalProcessUid(); + } final int appId = UserHandle.getAppId(uid); final SettingBase settingBase = mSettings.getSettingBase(appId); if (settingBase instanceof SharedUserSetting) { @@ -5594,4 +5623,8 @@ public class ComputerEngine implements Computer { return null; } } + + private int getSupplementalProcessUid() { + return getPackage(mService.getSupplementalProcessPackageName()).getUid(); + } } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 27605789891e..1cf2dc52e430 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -735,6 +735,9 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "unloadUserLocked: user=" + userId); } + // Cancel any ongoing background tasks. + getUserShortcutsLocked(userId).cancelAllInFlightTasks(); + // Save all dirty information. saveDirtyInfo(false); @@ -3736,6 +3739,7 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { mHandler.removeCallbacks(mSaveDirtyInfoRunner); + forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks); saveDirtyInfo(false); } mShutdown.set(true); diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 408f045f47b8..4bb5dcfa4b26 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -33,6 +33,7 @@ import android.util.Slog; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.logging.MetricsLogger; @@ -50,7 +51,9 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -138,6 +141,11 @@ class ShortcutUser { private String mLastAppScanOsFingerprint; private String mRestoreFromOsFingerprint; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final ArrayList<AndroidFuture<AppSearchSession>> mInFlightSessions = new ArrayList<>(); + public ShortcutUser(ShortcutService service, int userId) { mService = service; mUserId = userId; @@ -718,6 +726,10 @@ class ShortcutUser { AndroidFuture<AppSearchSession> getAppSearch( @NonNull final AppSearchManager.SearchContext searchContext) { final AndroidFuture<AppSearchSession> future = new AndroidFuture<>(); + synchronized (mLock) { + mInFlightSessions.removeIf(CompletableFuture::isDone); + mInFlightSessions.add(future); + } if (mAppSearchManager == null) { future.completeExceptionally(new RuntimeException("app search manager is null")); return future; @@ -743,4 +755,13 @@ class ShortcutUser { } return future; } + + void cancelAllInFlightTasks() { + synchronized (mLock) { + for (AndroidFuture<AppSearchSession> session : mInFlightSessions) { + session.cancel(true); + } + mInFlightSessions.clear(); + } + } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 38570727742d..e52315342399 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -521,6 +521,9 @@ public final class PowerManagerService extends SystemService // The screen off timeout setting value in milliseconds. private long mScreenOffTimeoutSetting; + // The screen off timeout setting value in milliseconds to apply while device is docked. + private long mScreenOffTimeoutDockedSetting; + // Default for attentive warning duration. private long mAttentiveWarningDurationConfig; @@ -1272,6 +1275,9 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.SCREEN_OFF_TIMEOUT), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_OFF_TIMEOUT_DOCKED), + false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.SLEEP_TIMEOUT), false, mSettingsObserver, UserHandle.USER_ALL); @@ -1394,6 +1400,9 @@ public final class PowerManagerService extends SystemService mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver, Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT, UserHandle.USER_CURRENT); + mScreenOffTimeoutDockedSetting = Settings.System.getLongForUser(resolver, + Settings.System.SCREEN_OFF_TIMEOUT_DOCKED, mScreenOffTimeoutSetting, + UserHandle.USER_CURRENT); mSleepTimeoutSetting = Settings.Secure.getIntForUser(resolver, Settings.Secure.SLEEP_TIMEOUT, DEFAULT_SLEEP_TIMEOUT, UserHandle.USER_CURRENT); @@ -2946,7 +2955,9 @@ public final class PowerManagerService extends SystemService @GuardedBy("mLock") private long getScreenOffTimeoutLocked(long sleepTimeout, long attentiveTimeout) { - long timeout = mScreenOffTimeoutSetting; + long timeout = mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED + ? mScreenOffTimeoutSetting + : mScreenOffTimeoutDockedSetting; if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin); } @@ -4974,7 +4985,8 @@ public final class PowerManagerService extends SystemService } } - private final class DockReceiver extends BroadcastReceiver { + @VisibleForTesting + final class DockReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index f15d2bb05c92..e02fabd7366b 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2543,6 +2543,7 @@ public final class TvInputManagerService extends SystemService { @Override public int getClientPriority(int useCase, String sessionId) { + ensureTunerResourceAccessPermission(); final int callingPid = Binder.getCallingPid(); final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ff96aebba708..ec4c58f9aa35 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1135,13 +1135,13 @@ class ActivityClientController extends IActivityClientController.Stub { } @Override - public void setDisablePreviewScreenshots(IBinder token, boolean disable) { + public void setRecentsScreenshotEnabled(IBinder token, boolean enabled) { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); if (r != null) { - r.setDisablePreviewScreenshots(disable); + r.setRecentsScreenshotEnabled(enabled); } } } finally { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 07692868dae5..fd5893623021 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -766,7 +766,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Last visibility state we reported to the app token. boolean reportedVisible; - boolean mDisablePreviewScreenshots; + boolean mEnablePreviewScreenshots = true; // Information about an application starting window if displayed. // Note: these are de-referenced before the starting window animates away. @@ -811,6 +811,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // How long we wait until giving up transfer splash screen. private static final int TRANSFER_SPLASH_SCREEN_TIMEOUT = 2000; + /** + * The icon is shown when the launching activity sets the splashScreenStyle to + * SPLASH_SCREEN_STYLE_ICON. If the launching activity does not specify any style, + * follow the system behavior. + * + * @see android.R.attr#windowSplashScreenBehavior + */ + private static final int SPLASH_SCREEN_BEHAVIOR_DEFAULT = 0; + /** + * The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY. + * + * @see android.R.attr#windowSplashScreenBehavior + */ + private static final int SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED = 1; + + @IntDef(prefix = {"SPLASH_SCREEN_BEHAVIOR_"}, value = { + SPLASH_SCREEN_BEHAVIOR_DEFAULT, + SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED + }) + @interface SplashScreenBehavior { } + // TODO: Have a WindowContainer state for tracking exiting/deferred removal. boolean mIsExiting; // Force an app transition to be ran in the case the visibility of the app did not change. @@ -2483,10 +2504,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void removeStartingWindow() { + boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation(); + if (transferSplashScreenIfNeeded()) { return; } removeStartingWindowAnimation(true /* prepareAnimation */); + + // TODO(b/215316431): Add tests + final Task task = getTask(); + if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation() + && task != null) { + // Trigger TaskInfoChanged to update the letterbox education. + task.dispatchTaskInfoChangedIfNeeded(true /* force */); + } } void removeStartingWindowAnimation(boolean prepareAnimation) { @@ -5116,22 +5147,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** - * See {@link Activity#setDisablePreviewScreenshots}. + * See {@link Activity#setRecentsScreenshotEnabled}. */ - void setDisablePreviewScreenshots(boolean disable) { - mDisablePreviewScreenshots = disable; + void setRecentsScreenshotEnabled(boolean enabled) { + mEnablePreviewScreenshots = enabled; } /** * Retrieves whether we'd like to generate a snapshot that's based solely on the theme. This is - * the case when preview screenshots are disabled {@link #setDisablePreviewScreenshots} or when + * the case when preview screenshots are disabled {@link #setRecentsScreenshotEnabled} or when * we can't take a snapshot for other reasons, for example, if we have a secure window. * * @return True if we need to generate an app theme snapshot, false if we'd like to take a real * screenshot. */ boolean shouldUseAppThemeSnapshot() { - return mDisablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked, + return !mEnablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked, true /* topToBottom */); } @@ -6594,8 +6625,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return null; } + private boolean isIconStylePreferred(int theme) { + if (theme == 0) { + return false; + } + final AttributeCache.Entry ent = AttributeCache.instance().get(packageName, theme, + R.styleable.Window, mWmService.mCurrentUserId); + if (ent != null) { + if (ent.array.hasValue(R.styleable.Window_windowSplashScreenBehavior)) { + return ent.array.getInt(R.styleable.Window_windowSplashScreenBehavior, + SPLASH_SCREEN_BEHAVIOR_DEFAULT) + == SPLASH_SCREEN_BEHAVIOR_ICON_PREFERRED; + } + } + return false; + } private boolean shouldUseEmptySplashScreen(ActivityRecord sourceRecord, boolean startActivity, - ActivityOptions options) { + ActivityOptions options, int resolvedTheme) { if (sourceRecord == null && !startActivity) { // Use empty style if this activity is not top activity. This could happen when adding // a splash screen window to the warm start activity which is re-create because top is @@ -6605,11 +6651,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return true; } } + + // setSplashScreenStyle decide in priority of windowSplashScreenBehavior. if (options != null) { final int optionsStyle = options.getSplashScreenStyle(); if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_EMPTY) { return true; - } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) { + } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON + || isIconStylePreferred(resolvedTheme)) { return false; } // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is @@ -6619,6 +6668,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { return true; } + } else if (isIconStylePreferred(resolvedTheme)) { + return false; } if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); @@ -6691,13 +6742,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - mSplashScreenStyleEmpty = shouldUseEmptySplashScreen( - sourceRecord, startActivity, startOptions); - final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0; final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme, splashScreenTheme); + mSplashScreenStyleEmpty = shouldUseEmptySplashScreen(sourceRecord, startActivity, + startOptions, resolvedTheme); + final boolean activityCreated = mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal(); // If this activity is just created and all activities below are finish, treat this @@ -7660,6 +7711,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * <li>The activity is eligible for fixed orientation letterbox. * <li>The activity is in fullscreen. * <li>The activity is portrait-only. + * <li>The activity doesn't have a starting window (education should only be displayed + * once the starting window is removed in {@link #removeStartingWindow}). * </ul> */ // TODO(b/215316431): Add tests @@ -7667,7 +7720,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mWmService.mLetterboxConfiguration.getIsEducationEnabled() && mIsEligibleForFixedOrientationLetterbox && getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT; + && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT + && mStartingWindow == null; } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ca4d7178fbcc..580ab1798f53 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5267,6 +5267,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** + * Returns {@code true} if the process represented by the pid passed as argument is + * instrumented. + */ + boolean isInstrumenting(int pid) { + final WindowProcessController process; + synchronized (mGlobalLock) { + process = mProcessMap.getProcess(pid); + } + return process != null ? process.isInstrumenting() : false; + } + final class H extends Handler { static final int REPORT_TIME_TRACKER_MSG = 1; static final int UPDATE_PROCESS_ANIMATING_STATE = 2; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 092cff384a80..a25dc42086a8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5761,7 +5761,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration); super.onRequestedOverrideConfigurationChanged(overrideConfiguration); mCurrentOverrideConfigurationChanges = 0; - mWmService.setNewDisplayOverrideConfiguration(currOverrideConfig, this); + if (mWaitingForConfig) { + mWaitingForConfig = false; + mWmService.mLastFinishedFreezeSource = "new-config"; + } mAtmService.addWindowLayoutReasons( ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 2a05d05d4786..6438d79e5761 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.util.RotationUtils.deltaRotation; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; @@ -39,7 +40,6 @@ import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_D import android.annotation.AnimRes; import android.annotation.IntDef; -import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; @@ -50,7 +50,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.power.Boost; -import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.SystemProperties; @@ -58,6 +57,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; +import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IDisplayWindowRotationCallback; import android.view.IWindowManager; @@ -77,6 +77,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayDeque; /** * Defines the mapping between orientation and rotation of a display. @@ -106,6 +107,7 @@ public class DisplayRotation { private final int mDeskDockRotation; private final int mUndockedHdmiRotation; private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair(); + private final RotationHistory mRotationHistory = new RotationHistory(); private OrientationListener mOrientationListener; private StatusBarManagerInternal mStatusBarManagerInternal; @@ -139,6 +141,8 @@ public class DisplayRotation { @VisibleForTesting int mUpsideDownRotation; // "other" portrait + int mLastSensorRotation = -1; + private boolean mAllowSeamlessRotationDespiteNavBarMoving; private int mDeferredRotationPauseCount; @@ -351,6 +355,7 @@ public class DisplayRotation { } void applyCurrentRotation(@Surface.Rotation int rotation) { + mRotationHistory.addRecord(this, rotation); if (mOrientationListener != null) { mOrientationListener.setCurrentRotation(rotation); } @@ -1149,6 +1154,7 @@ public class DisplayRotation { int sensorRotation = mOrientationListener != null ? mOrientationListener.getProposedRotation() // may be -1 : -1; + mLastSensorRotation = sensorRotation; if (sensorRotation < 0) { sensorRotation = lastRotation; } @@ -1537,6 +1543,15 @@ public class DisplayRotation { pw.println(" mUndockedHdmiRotation=" + Surface.rotationToString(mUndockedHdmiRotation)); pw.println(prefix + " mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation)); pw.println(prefix + " mFixedToUserRotation=" + isFixedToUserRotation()); + + if (!mRotationHistory.mRecords.isEmpty()) { + pw.println(); + pw.println(prefix + " RotationHistory"); + prefix = " " + prefix; + for (RotationHistory.Record r : mRotationHistory.mRecords) { + r.dump(prefix, pw); + } + } } void dumpDebug(ProtoOutputStream proto, long fieldId) { @@ -1650,9 +1665,69 @@ public class DisplayRotation { } } - @VisibleForTesting - interface ContentObserverRegister { - void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver observer, @UserIdInt int userHandle); + private static class RotationHistory { + private static final int MAX_SIZE = 8; + private static class Record { + final @Surface.Rotation int mFromRotation; + final @Surface.Rotation int mToRotation; + final @Surface.Rotation int mUserRotation; + final @WindowManagerPolicy.UserRotationMode int mUserRotationMode; + final int mSensorRotation; + final boolean mIgnoreOrientationRequest; + final String mNonDefaultRequestingTaskDisplayArea; + final String mLastOrientationSource; + final @ActivityInfo.ScreenOrientation int mSourceOrientation; + final long mTimestamp = System.currentTimeMillis(); + + Record(DisplayRotation dr, int fromRotation, int toRotation) { + mFromRotation = fromRotation; + mToRotation = toRotation; + mUserRotation = dr.mUserRotation; + mUserRotationMode = dr.mUserRotationMode; + final OrientationListener listener = dr.mOrientationListener; + mSensorRotation = (listener == null || !listener.mEnabled) + ? -2 /* disabled */ : dr.mLastSensorRotation; + final DisplayContent dc = dr.mDisplayContent; + mIgnoreOrientationRequest = dc.mIgnoreOrientationRequest; + final TaskDisplayArea requestingTda = dc.getOrientationRequestingTaskDisplayArea(); + mNonDefaultRequestingTaskDisplayArea = requestingTda == null + ? "none" : requestingTda != dc.getDefaultTaskDisplayArea() + ? requestingTda.toString() : null; + final WindowContainer<?> source = dc.getLastOrientationSource(); + if (source != null) { + mLastOrientationSource = source.toString(); + mSourceOrientation = source.mOrientation; + } else { + mLastOrientationSource = null; + mSourceOrientation = SCREEN_ORIENTATION_UNSET; + } + } + + void dump(String prefix, PrintWriter pw) { + pw.println(prefix + TimeUtils.logTimeOfDay(mTimestamp) + + " " + Surface.rotationToString(mFromRotation) + + " to " + Surface.rotationToString(mToRotation)); + pw.println(prefix + " source=" + mLastOrientationSource + + " " + ActivityInfo.screenOrientationToString(mSourceOrientation)); + pw.println(prefix + " mode=" + + WindowManagerPolicy.userRotationModeToString(mUserRotationMode) + + " user=" + Surface.rotationToString(mUserRotation) + + " sensor=" + Surface.rotationToString(mSensorRotation)); + if (mIgnoreOrientationRequest) pw.println(prefix + " ignoreRequest=true"); + if (mNonDefaultRequestingTaskDisplayArea != null) { + pw.println(prefix + " requestingTda=" + mNonDefaultRequestingTaskDisplayArea); + } + } + } + + final ArrayDeque<Record> mRecords = new ArrayDeque<>(MAX_SIZE); + + void addRecord(DisplayRotation dr, int toRotation) { + if (mRecords.size() >= MAX_SIZE) { + mRecords.removeFirst(); + } + final int fromRotation = dr.mDisplayContent.getWindowConfiguration().getRotation(); + mRecords.addLast(new Record(dr, fromRotation, toRotation)); + } } } diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 8d3e07116693..aa5e20d9ec7f 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -104,7 +104,8 @@ class EnsureActivitiesVisibleHelper { for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mTaskFragment.mChildren.get(i); final TaskFragment childTaskFragment = child.asTaskFragment(); - if (childTaskFragment != null && childTaskFragment.topRunningActivity() != null) { + if (childTaskFragment != null + && childTaskFragment.getTopNonFinishingActivity() != null) { childTaskFragment.updateActivityVisibilities(starting, configChanges, preserveWindows, notifyClients); mBehindFullyOccludedContainer |= diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 1183094c4fa3..c1835a0f3557 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -18,8 +18,7 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.RemoteAnimationTarget.MODE_CLOSING; @@ -129,7 +128,6 @@ public class RecentsAnimationController implements DeathRecipient { private ActivityRecord mTargetActivityRecord; private DisplayContent mDisplayContent; private int mTargetActivityType; - private Rect mMinimizedHomeBounds = new Rect(); // We start the RecentsAnimationController in a pending-start state since we need to wait for // the wallpaper/activity to draw before we can give control to the handler to start animating @@ -480,11 +478,6 @@ public class RecentsAnimationController implements DeathRecipient { mDisplayContent.setLayoutNeeded(); } - // Save the minimized home height - final Task rootHomeTask = - mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); - mMinimizedHomeBounds = rootHomeTask != null ? rootHomeTask.getBounds() : null; - mService.mWindowPlacerLocked.performSurfacePlacement(); mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity); @@ -502,9 +495,7 @@ public class RecentsAnimationController implements DeathRecipient { */ private boolean skipAnimation(Task task) { final WindowConfiguration config = task.getWindowConfiguration(); - return task.isAlwaysOnTop() - || config.tasksAreFloating() - || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; + return task.isAlwaysOnTop() || config.tasksAreFloating(); } @VisibleForTesting @@ -568,10 +559,6 @@ public class RecentsAnimationController implements DeathRecipient { // insets for the target app window after a rotation mDisplayContent.performLayout(false /* initial */, false /* updateInputWindows */); - final Rect minimizedHomeBounds = mTargetActivityRecord != null - && mTargetActivityRecord.inSplitScreenSecondaryWindowingMode() - ? mMinimizedHomeBounds - : null; final Rect contentInsets; final WindowState targetAppMainWindow = getTargetAppMainWindow(); if (targetAppMainWindow != null) { @@ -585,7 +572,7 @@ public class RecentsAnimationController implements DeathRecipient { contentInsets = mTmpRect; } mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets, - minimizedHomeBounds); + null); ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startAnimation(): Notify animation start: %s", mPendingAnimations.stream() @@ -623,16 +610,17 @@ public class RecentsAnimationController implements DeathRecipient { for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter adapter = mPendingAnimations.get(i); final Task task = adapter.mTask; - final boolean isSplitScreenSecondary = - task.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; + final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment(); + final boolean inSplitScreen = task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW + && adjacentTask != null; if (task.isHomeOrRecentsRootTask() - // TODO(b/178449492): Will need to update for the new split screen mode once - // it's ready. - // Skip if the task is the secondary split screen and in landscape. - || (isSplitScreenSecondary && isDisplayLandscape)) { + // Skip if the task is in split screen and in landscape. + || (inSplitScreen && isDisplayLandscape) + // Skip if the task is the top task in split screen. + || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) { continue; } - shouldTranslateNavBar = isSplitScreenSecondary; + shouldTranslateNavBar = inSplitScreen; mNavBarAttachedApp = task.getTopVisibleActivity(); break; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 76a7981fa946..d5350183cdcb 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -637,36 +637,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - /** - * Set new display override config. If called for the default display, global configuration - * will also be updated. - */ - void setDisplayOverrideConfigurationIfNeeded(Configuration newConfiguration, - @NonNull DisplayContent displayContent) { - - final Configuration currentConfig = displayContent.getRequestedOverrideConfiguration(); - final boolean configChanged = currentConfig.diff(newConfiguration) != 0; - if (!configChanged) { - return; - } - - displayContent.onRequestedOverrideConfigurationChanged(newConfiguration); - - if (displayContent.getDisplayId() == DEFAULT_DISPLAY) { - // Override configuration of the default display duplicates global config. In this case - // we also want to update the global config. - setGlobalConfigurationIfNeeded(newConfiguration); - } - } - - private void setGlobalConfigurationIfNeeded(Configuration newConfiguration) { - final boolean configChanged = getConfiguration().diff(newConfiguration) != 0; - if (!configChanged) { - return; - } - onConfigurationChanged(newConfiguration); - } - @Override void dispatchConfigurationToChild(DisplayContent child, Configuration config) { if (child.isDefaultDisplay) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 177d2e61d5f0..a59d7b6e7219 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -945,6 +945,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { // we still want to check if the visibility of other windows have changed (e.g. bringing // a fullscreen window forward to cover another freeform activity.) if (taskDisplayArea.inMultiWindowMode()) { + if (taskDisplayArea.mDisplayContent != null + && taskDisplayArea.mDisplayContent.mFocusedApp != next) { + taskDisplayArea.mDisplayContent.setFocusedApp(next); + } taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, false /* preserveWindows */, true /* notifyClients */); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index bf11ebc8c95f..a9add59be1f6 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -196,15 +196,21 @@ class TaskSnapshotController { snapshotTasks(tasks, false /* allowSnapshotHome */); } - void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { + /** + * This is different than {@link #recordTaskSnapshot(Task, boolean)} because it doesn't store + * the snapshot to the cache and returns the TaskSnapshot immediately. + * + * This is only used for testing so the snapshot content can be verified. + */ + @VisibleForTesting + TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) { final TaskSnapshot snapshot; - final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); if (snapshotHome) { snapshot = snapshotTask(task); } else { switch (getSnapshotMode(task)) { case SNAPSHOT_MODE_NONE: - return; + return null; case SNAPSHOT_MODE_APP_THEME: snapshot = drawAppThemeSnapshot(task); break; @@ -216,19 +222,27 @@ class TaskSnapshotController { break; } } - if (snapshot != null) { - final HardwareBuffer buffer = snapshot.getHardwareBuffer(); - if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { - buffer.close(); - Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" - + buffer.getHeight()); - } else { - mCache.putSnapshot(task, snapshot); - // Don't persist or notify the change for the temporal snapshot. - if (!snapshotHome) { - mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); - task.onSnapshotChanged(snapshot); - } + return snapshot; + } + + void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { + final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); + final TaskSnapshot snapshot = captureTaskSnapshot(task, snapshotHome); + if (snapshot == null) { + return; + } + + final HardwareBuffer buffer = snapshot.getHardwareBuffer(); + if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { + buffer.close(); + Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" + + buffer.getHeight()); + } else { + mCache.putSnapshot(task, snapshot); + // Don't persist or notify the change for the temporal snapshot. + if (!snapshotHome) { + mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); + task.onSnapshotChanged(snapshot); } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 71f48824b9e4..b8654600a61c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -116,6 +116,7 @@ import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; +import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; @@ -2892,16 +2893,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void setNewDisplayOverrideConfiguration(Configuration overrideConfig, - @NonNull DisplayContent dc) { - if (dc.mWaitingForConfig) { - dc.mWaitingForConfig = false; - mLastFinishedFreezeSource = "new-config"; - } - - mRoot.setDisplayOverrideConfigurationIfNeeded(overrideConfig, dc); - } - // TODO(multi-display): remove when no default display use case. void prepareAppTransitionNone() { if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) { @@ -3682,7 +3673,8 @@ public class WindowManagerService extends IWindowManager.Stub * Sets the touch mode state. * * To be able to change touch mode state, the caller must either own the focused window, or must - * have the MODIFY_TOUCH_MODE_STATE permission. + * have the MODIFY_TOUCH_MODE_STATE permission. Instrumented processes are allowed to switch + * touch mode at any time. * * @param mode the touch mode to set */ @@ -3694,8 +3686,9 @@ public class WindowManagerService extends IWindowManager.Stub } final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); - final boolean hasPermission = checkCallingPermission(MODIFY_TOUCH_MODE_STATE, - "setInTouchMode()"); + + final boolean hasPermission = mAtmService.isInstrumenting(pid) + || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()"); final long token = Binder.clearCallingIdentity(); try { if (mInputManager.setInTouchMode(mode, pid, uid, hasPermission)) { @@ -8908,4 +8901,27 @@ public class WindowManagerService extends IWindowManager.Stub mTaskFpsCallbackController.unregisterCallback(listener); } + + @Override + public Bitmap snapshotTaskForRecents(int taskId) { + if (!checkCallingPermission(READ_FRAME_BUFFER, "snapshotTaskForRecents()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + + TaskSnapshot taskSnapshot; + synchronized (mGlobalLock) { + Task task = mRoot.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS); + if (task == null) { + throw new IllegalArgumentException( + "Failed to find matching task for taskId=" + taskId); + } + taskSnapshot = mTaskSnapshotController.captureTaskSnapshot(task, false); + } + + if (taskSnapshot == null || taskSnapshot.getHardwareBuffer() == null) { + return null; + } + return Bitmap.wrapHardwareBuffer(taskSnapshot.getHardwareBuffer(), + taskSnapshot.getColorSpace()); + } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 27024ce77bd2..1d5c1841fde1 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1146,7 +1146,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final PendingTransaction pt = mPendingTransactions.remove(0); pt.startSync(); // Post this so that the now-playing transition setup isn't interrupted. - mService.mH.post(pt::startTransaction); + mService.mH.post(() -> { + synchronized (mGlobalLock) { + pt.startTransaction(); + } + }); } @Override diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 11a6141c6c6d..498eaabb9386 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2385,6 +2385,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP dc.getDisplayPolicy().removeWindowLw(this); disposeInputChannel(); + mOnBackInvokedCallback = null; mSession.windowRemovedLocked(); try { @@ -2438,6 +2439,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { disposeInputChannel(); + mOnBackInvokedCallback = null; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b " diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index adc91fc3f2e8..8197b67355d4 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -243,22 +243,34 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, c xAbsSetup.code = ABS_MT_POSITION_X; xAbsSetup.absinfo.maximum = screenWidth - 1; xAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, xAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno)); + return -errno; + } uinput_abs_setup yAbsSetup; yAbsSetup.code = ABS_MT_POSITION_Y; yAbsSetup.absinfo.maximum = screenHeight - 1; yAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, yAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno)); + return -errno; + } uinput_abs_setup majorAbsSetup; majorAbsSetup.code = ABS_MT_TOUCH_MAJOR; majorAbsSetup.absinfo.maximum = screenWidth - 1; majorAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, majorAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno)); + return -errno; + } uinput_abs_setup pressureAbsSetup; pressureAbsSetup.code = ABS_MT_PRESSURE; pressureAbsSetup.absinfo.maximum = 255; pressureAbsSetup.absinfo.minimum = 0; - ioctl(fd, UI_ABS_SETUP, pressureAbsSetup); + if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) { + ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno)); + return -errno; + } } if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) { ALOGE("Error creating uinput device: %s", strerror(errno)); @@ -266,6 +278,7 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, c } } else { // UI_DEV_SETUP was not introduced until version 5. Try setting up manually. + ALOGI("Falling back to version %d manual setup", version); uinput_user_dev fallback; memset(&fallback, 0, sizeof(fallback)); strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ebfa2c6d85ea..f8ace0cb797b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9616,18 +9616,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Cannot set the profile owner on a user which is already set-up"); if (!mIsWatch) { - // Only the default supervision profile owner can be set as profile owner after SUW + final String supervisionRolePackage = mContext.getResources().getString( + com.android.internal.R.string.config_systemSupervision); + // Only the default supervision profile owner or supervision role holder + // can be set as profile owner after SUW final String supervisor = mContext.getResources().getString( com.android.internal.R.string .config_defaultSupervisionProfileOwnerComponent); - if (supervisor == null) { + if (supervisor == null && supervisionRolePackage == null) { throw new IllegalStateException("Unable to set profile owner post-setup, no" + "default supervisor profile owner defined"); } final ComponentName supervisorComponent = ComponentName.unflattenFromString( supervisor); - if (!owner.equals(supervisorComponent)) { + if (!owner.equals(supervisorComponent) + && !owner.getPackageName().equals(supervisionRolePackage)) { throw new IllegalStateException("Unable to set non-default profile owner" + " post-setup " + owner); } @@ -10982,13 +10986,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public void acknowledgeNewUserDisclaimer() { + public void acknowledgeNewUserDisclaimer(@UserIdInt int userId) { CallerIdentity callerIdentity = getCallerIdentity(); Preconditions.checkCallAuthorization(canManageUsers(callerIdentity) || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS)); - setShowNewUserDisclaimer(callerIdentity.getUserId(), - DevicePolicyData.NEW_USER_DISCLAIMER_ACKNOWLEDGED); + setShowNewUserDisclaimer(userId, DevicePolicyData.NEW_USER_DISCLAIMER_ACKNOWLEDGED); } private void setShowNewUserDisclaimer(@UserIdInt int userId, String value) { @@ -11021,11 +11024,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean isNewUserDisclaimerAcknowledged() { + public boolean isNewUserDisclaimerAcknowledged(@UserIdInt int userId) { CallerIdentity callerIdentity = getCallerIdentity(); Preconditions.checkCallAuthorization(canManageUsers(callerIdentity) || hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS)); - int userId = callerIdentity.getUserId(); synchronized (getLockObject()) { DevicePolicyData policyData = getUserData(userId); return policyData.isNewUserDisclaimerAcknowledged(); diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 62a16f7f24fd..c5f990d52b82 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -60,6 +60,12 @@ public final class ProfcollectForwardingService extends SystemService { private static ProfcollectForwardingService sSelfService; private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper()); + private IProviderStatusCallback mProviderStatusCallback = new IProviderStatusCallback.Stub() { + public void onProviderReady() { + mHandler.sendEmptyMessage(ProfcollectdHandler.MESSAGE_REGISTER_SCHEDULERS); + } + }; + public ProfcollectForwardingService(Context context) { super(context); @@ -93,13 +99,23 @@ public final class ProfcollectForwardingService extends SystemService { } BackgroundThread.get().getThreadHandler().post(() -> { if (serviceHasSupportedTraceProvider()) { - registerObservers(); - ProfcollectBGJobService.schedule(getContext()); + registerProviderStatusCallback(); } }); } } + private void registerProviderStatusCallback() { + if (mIProfcollect == null) { + return; + } + try { + mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback); + } catch (RemoteException e) { + Log.e(LOG_TAG, e.getMessage()); + } + } + private boolean serviceHasSupportedTraceProvider() { if (mIProfcollect == null) { return false; @@ -141,6 +157,7 @@ public final class ProfcollectForwardingService extends SystemService { } public static final int MESSAGE_BINDER_CONNECT = 0; + public static final int MESSAGE_REGISTER_SCHEDULERS = 1; @Override public void handleMessage(android.os.Message message) { @@ -148,6 +165,10 @@ public final class ProfcollectForwardingService extends SystemService { case MESSAGE_BINDER_CONNECT: connectNativeService(); break; + case MESSAGE_REGISTER_SCHEDULERS: + registerObservers(); + ProfcollectBGJobService.schedule(getContext()); + break; default: throw new AssertionError("Unknown message: " + message); } diff --git a/services/proguard.flags b/services/proguard.flags index 5d01d3e7f85c..0e081f182d0d 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -1,21 +1,105 @@ -# TODO(b/196084106): Refine and optimize this configuration. Note that this +# TODO(b/210510433): Refine and optimize this configuration. Note that this # configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`. --keep,allowoptimization,allowaccessmodification class ** { - !synthetic *; + +# Preserve line number information for debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# Allows making private and protected methods/fields public as part of +# optimization. This enables inlining of trivial getter/setter methods. +-allowaccessmodification + +# Process entrypoint +-keep class com.android.server.SystemServer { + public static void main(java.lang.String[]); } -# Various classes subclassed in ethernet-service (avoid marking final). +# APIs referenced by dependent JAR files and modules +-keep @interface android.annotation.SystemApi +-keep @android.annotation.SystemApi class * { + public protected *; +} +-keepclasseswithmembers class * { + @android.annotation.SystemApi *; +} + +# Derivatives of SystemService and other services created via reflection +-keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService { + public <methods>; +} +-keep,allowoptimization,allowaccessmodification class * extends com.android.server.devicepolicy.BaseIDevicePolicyManager { + public <init>(...); +} +-keep,allowoptimization,allowaccessmodification class com.android.server.wallpaper.WallpaperManagerService { + public <init>(...); +} + +# Binder interfaces +-keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface +-keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface + +# Global entities normally kept through explicit Manifest entries +# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/AndroidManifest.xml, +# by including that manifest with the library rule that triggers optimization. +-keep,allowoptimization,allowaccessmodification class * extends android.app.backup.BackupAgent +-keep,allowoptimization,allowaccessmodification class * extends android.content.BroadcastReceiver +-keep,allowoptimization,allowaccessmodification class * extends android.content.ContentProvider + +# Various classes subclassed in or referenced via JNI in ethernet-service -keep public class android.net.** { *; } +-keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; } +-keep,allowoptimization,allowaccessmodification public class com.android.server.net.IpConfigStore { *; } +-keep,allowoptimization,allowaccessmodification public class com.android.server.net.BaseNetworkObserver { *; } -# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing). +# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing) -keep public class com.android.server.utils.Slogf { *; } -# Allows making private and protected methods/fields public as part of -# optimization. This enables inlining of trivial getter/setter methods. --allowaccessmodification +# Notification extractors +# TODO(b/210510433): Revisit and consider generating from frameworks/base/core/res/res/values/config.xml. +-keep,allowoptimization,allowaccessmodification public class com.android.server.notification.** implements com.android.server.notification.NotificationSignalExtractor -# Disallow accessmodification for soundtrigger classes. Logging via reflective -# public member traversal can cause infinite loops. See b/210901706. --keep,allowoptimization class com.android.server.soundtrigger_middleware.** { - !synthetic *; +# JNI keep rules +# TODO(b/210510433): Revisit and fix with @Keep, or consider auto-generating from +# frameworks/base/services/core/jni/onload.cpp. +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.BroadcastRadioService { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.Convert { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.Tuner { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.TunerCallback { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssConfiguration$HalInterfaceVersion { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.ExternalCaptureStateTracker { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.storage.AppFuseBridge { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.tv.TvInputHal { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbMidiDevice { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; } +-keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** { + *** *FromNative(...); } +-keep,allowoptimization,allowaccessmodification class com.android.server.input.InputManagerService { + <methods>; +} +-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbHostManager { + *** usbDeviceRemoved(...); + *** usbDeviceAdded(...); +} +-keep,allowoptimization,allowaccessmodification class **.*NativeWrapper* { *; } + +# Miscellaneous reflection keep rules +# TODO(b/210510433): Revisit and fix with @Keep. +-keep,allowoptimization,allowaccessmodification class com.android.server.usage.AppStandbyController { + public <init>(...); +} +-keep,allowoptimization,allowaccessmodification class android.hardware.usb.gadget.** { *; } + +# Needed when optimizations enabled +# TODO(b/210510433): Revisit and fix with @Keep. +-keep,allowoptimization,allowaccessmodification class com.android.server.SystemService { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.SystemService$TargetUser { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.usage.StorageStatsManagerLocal { *; } +-keep,allowoptimization,allowaccessmodification class com.android.internal.util.** { *; } +-keep,allowoptimization,allowaccessmodification class android.os.** { *; } diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index d71030802c2b..ac115a26f9e4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -30,7 +30,6 @@ import static com.android.server.DeviceIdleController.LIGHT_STATE_IDLE; import static com.android.server.DeviceIdleController.LIGHT_STATE_IDLE_MAINTENANCE; import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE; import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE; -import static com.android.server.DeviceIdleController.LIGHT_STATE_PRE_IDLE; import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK; import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS; import static com.android.server.DeviceIdleController.MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR; @@ -112,6 +111,7 @@ import java.util.concurrent.Executor; /** * Tests for {@link com.android.server.DeviceIdleController}. */ +@SuppressWarnings("GuardedBy") @RunWith(AndroidJUnit4.class) public class DeviceIdleControllerTest { private DeviceIdleController mDeviceIdleController; @@ -875,7 +875,7 @@ public class DeviceIdleControllerTest { @Test public void testLightStepIdleStateLocked_InvalidStates() { mDeviceIdleController.becomeActiveLocked("testing", 0); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); // stepLightIdleStateLocked doesn't handle the ACTIVE case, so the state // should stay as ACTIVE. verifyLightStateConditions(LIGHT_STATE_ACTIVE); @@ -888,7 +888,7 @@ public class DeviceIdleControllerTest { @Test public void testLightStepIdleStateLocked_Overriden() { enterLightState(LIGHT_STATE_OVERRIDE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_OVERRIDE); } @@ -906,18 +906,18 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_INACTIVE); // No active ops means INACTIVE should go straight to IDLE. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); // Should just alternate between IDLE and IDLE_MAINTENANCE now. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); } @@ -930,26 +930,22 @@ public class DeviceIdleControllerTest { setScreenOn(false); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - // Active ops means INACTIVE should go to PRE_IDLE to wait. + // After enough time, INACTIVE should go to IDLE regardless of any active ops. mDeviceIdleController.setJobsActive(true); mDeviceIdleController.setAlarmsActive(true); mDeviceIdleController.setActiveIdleOpsForTest(1); - mDeviceIdleController.stepLightIdleStateLocked("testing"); - verifyLightStateConditions(LIGHT_STATE_PRE_IDLE); - - // Even with active ops, PRE_IDLE should go to IDLE. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); // Should just alternate between IDLE and IDLE_MAINTENANCE now. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); } @@ -967,24 +963,24 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_INACTIVE); // No active ops means INACTIVE should go straight to IDLE. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); // Should cycle between IDLE, WAITING_FOR_NETWORK, and IDLE_MAINTENANCE now. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); } @@ -997,36 +993,177 @@ public class DeviceIdleControllerTest { setScreenOn(false); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - // Active ops means INACTIVE should go to PRE_IDLE to wait. + // After enough time, INACTIVE should go to IDLE regardless of any active ops. mDeviceIdleController.setJobsActive(true); mDeviceIdleController.setAlarmsActive(true); mDeviceIdleController.setActiveIdleOpsForTest(1); - mDeviceIdleController.stepLightIdleStateLocked("testing"); - verifyLightStateConditions(LIGHT_STATE_PRE_IDLE); - - // Even with active ops, PRE_IDLE should go to IDLE. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); // Should cycle between IDLE, WAITING_FOR_NETWORK, and IDLE_MAINTENANCE now. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); } @Test + public void testLightStepIdleStateSkippedAlarms() { + setNetworkConnected(true); + mDeviceIdleController.setJobsActive(false); + mDeviceIdleController.setAlarmsActive(false); + mDeviceIdleController.setActiveIdleOpsForTest(0); + + final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor = ArgumentCaptor + .forClass(AlarmManager.OnAlarmListener.class); + doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any()); + doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any()); + + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyLightStateConditions(LIGHT_STATE_INACTIVE); + + final AlarmManager.OnAlarmListener progressionListener = + alarmListenerCaptor.getAllValues().get(0); + final AlarmManager.OnAlarmListener maintenanceListener = + alarmListenerCaptor.getAllValues().get(1); + + // Set things to make it look like the INACTIVE -> IDLE alarm didn't fire and the + // MAINTENANCE alarm just fired. + mInjector.nowElapsed = mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting(); + // If the non-wakeup alarm doesn't fire in a timely manner, we would see both fire at the + // same time. + progressionListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); + maintenanceListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); + + assertTrue(mInjector.nowElapsed < mDeviceIdleController.getNextLightAlarmTimeForTesting()); + + // MAINTENANCE->IDLE alarm goes off at correct time. + mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting(); + progressionListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE); + + // Go back to MAINTENANCE + mInjector.nowElapsed = mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting(); + maintenanceListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); + + assertTrue(mInjector.nowElapsed < mDeviceIdleController.getNextLightAlarmTimeForTesting()); + assertTrue(mInjector.nowElapsed + < mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting()); + + // MAINTENANCE->IDLE alarm is delayed until IDLE->MAINTENANCE alarm goes off. + mInjector.nowElapsed = mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting(); + progressionListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); + maintenanceListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); + } + + @Test + public void testLightStepIdleStateIdlingTimeIncreases() { + final long maintenanceTimeMs = 60_000L; + mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = maintenanceTimeMs; + mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = maintenanceTimeMs; + mConstants.LIGHT_IDLE_TIMEOUT = 5 * 60_000L; + mConstants.LIGHT_MAX_IDLE_TIMEOUT = 20 * 60_000L; + mConstants.LIGHT_IDLE_FACTOR = 2f; + + setNetworkConnected(true); + mDeviceIdleController.setJobsActive(false); + mDeviceIdleController.setAlarmsActive(false); + mDeviceIdleController.setActiveIdleOpsForTest(0); + + InOrder alarmManagerInOrder = inOrder(mAlarmManager); + + final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor = ArgumentCaptor + .forClass(AlarmManager.OnAlarmListener.class); + doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any()); + doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), + eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any()); + + // Set state to INACTIVE. + mDeviceIdleController.becomeActiveLocked("testing", 0); + setChargingOn(false); + setScreenOn(false); + verifyLightStateConditions(LIGHT_STATE_INACTIVE); + long idlingTimeMs = mConstants.LIGHT_IDLE_TIMEOUT; + final long idleAfterInactiveExpiryTime = + mInjector.nowElapsed + mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT; + alarmManagerInOrder.verify(mAlarmManager).setWindow( + eq(AlarmManager.ELAPSED_REALTIME), + eq(idleAfterInactiveExpiryTime), + anyLong(), anyString(), any(), any()); + // Maintenance alarm + alarmManagerInOrder.verify(mAlarmManager).setWindow( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + eq(idleAfterInactiveExpiryTime + idlingTimeMs), + anyLong(), anyString(), any(), any()); + + final AlarmManager.OnAlarmListener progressionListener = + alarmListenerCaptor.getAllValues().get(0); + final AlarmManager.OnAlarmListener maintenanceListener = + alarmListenerCaptor.getAllValues().get(1); + + // INACTIVE -> IDLE alarm + mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting(); + progressionListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE); + alarmManagerInOrder.verify(mAlarmManager).setWindow( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + eq(mInjector.nowElapsed + idlingTimeMs), + anyLong(), anyString(), any(), any()); + + for (int i = 0; i < 2; ++i) { + // IDLE->MAINTENANCE alarm + mInjector.nowElapsed = + mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting(); + maintenanceListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); + long maintenanceExpiryTime = mInjector.nowElapsed + maintenanceTimeMs; + idlingTimeMs *= mConstants.LIGHT_IDLE_FACTOR; + // Set MAINTENANCE->IDLE + alarmManagerInOrder.verify(mAlarmManager).setWindow( + eq(AlarmManager.ELAPSED_REALTIME), + eq(maintenanceExpiryTime), + anyLong(), anyString(), any(), any()); + // Set IDLE->MAINTENANCE + alarmManagerInOrder.verify(mAlarmManager).setWindow( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + eq(maintenanceExpiryTime + idlingTimeMs), + anyLong(), anyString(), any(), any()); + + // MAINTENANCE->IDLE alarm + mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting(); + progressionListener.onAlarm(); + verifyLightStateConditions(LIGHT_STATE_IDLE); + // Set IDLE->MAINTENANCE again + alarmManagerInOrder.verify(mAlarmManager).setWindow( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + eq(mInjector.nowElapsed + idlingTimeMs), + anyLong(), anyString(), any(), any()); + } + } + + @Test public void testLightIdleAlarmUnaffectedByMotion() { setNetworkConnected(true); mDeviceIdleController.setJobsActive(false); @@ -1043,45 +1180,37 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_INACTIVE); // No active ops means INACTIVE should go straight to IDLE. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( - longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT), - longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX), - eq(false)); + inOrder.verify(mDeviceIdleController).scheduleLightMaintenanceAlarmLocked( + longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT)); // Should just alternate between IDLE and IDLE_MAINTENANCE now. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET), - longThat(l -> l == mConstants.FLEX_TIME_SHORT), - eq(true)); + longThat(l -> l == mConstants.FLEX_TIME_SHORT)); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( - longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT), - longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX), - eq(false)); + inOrder.verify(mDeviceIdleController).scheduleLightMaintenanceAlarmLocked( + longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT)); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET), - longThat(l -> l == mConstants.FLEX_TIME_SHORT), - eq(true)); + longThat(l -> l == mConstants.FLEX_TIME_SHORT)); // Test that motion doesn't reset the idle timeout. mDeviceIdleController.handleMotionDetectedLocked(50, "test"); - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); verifyLightStateConditions(LIGHT_STATE_IDLE); - inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( - longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT), - longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX), - eq(false)); + inOrder.verify(mDeviceIdleController).scheduleLightMaintenanceAlarmLocked( + longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT)); } ///////////////// EXIT conditions /////////////////// @@ -1268,10 +1397,6 @@ public class DeviceIdleControllerTest { mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - enterLightState(LIGHT_STATE_PRE_IDLE); - mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); - verifyLightStateConditions(LIGHT_STATE_IDLE); - enterLightState(LIGHT_STATE_IDLE); mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_IDLE); @@ -1307,10 +1432,6 @@ public class DeviceIdleControllerTest { mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - enterLightState(LIGHT_STATE_PRE_IDLE); - mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); - verifyLightStateConditions(LIGHT_STATE_PRE_IDLE); - enterLightState(LIGHT_STATE_IDLE); mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_IDLE); @@ -1344,10 +1465,6 @@ public class DeviceIdleControllerTest { mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - enterLightState(LIGHT_STATE_PRE_IDLE); - mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); - verifyLightStateConditions(LIGHT_STATE_PRE_IDLE); - enterLightState(LIGHT_STATE_IDLE); mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_IDLE); @@ -1381,10 +1498,6 @@ public class DeviceIdleControllerTest { mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - enterLightState(LIGHT_STATE_PRE_IDLE); - mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); - verifyLightStateConditions(LIGHT_STATE_PRE_IDLE); - enterLightState(LIGHT_STATE_IDLE); mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked(); verifyLightStateConditions(LIGHT_STATE_IDLE); @@ -1510,10 +1623,6 @@ public class DeviceIdleControllerTest { mDeviceIdleController.handleMotionDetectedLocked(50, "test"); verifyLightStateConditions(LIGHT_STATE_INACTIVE); - enterLightState(LIGHT_STATE_PRE_IDLE); - mDeviceIdleController.handleMotionDetectedLocked(50, "test"); - verifyLightStateConditions(LIGHT_STATE_PRE_IDLE); - enterLightState(LIGHT_STATE_IDLE); mDeviceIdleController.handleMotionDetectedLocked(50, "test"); verifyLightStateConditions(LIGHT_STATE_IDLE); @@ -1580,10 +1689,6 @@ public class DeviceIdleControllerTest { mDeviceIdleController.becomeActiveLocked("test", 1000); verifyLightStateConditions(LIGHT_STATE_ACTIVE); - enterLightState(LIGHT_STATE_PRE_IDLE); - mDeviceIdleController.becomeActiveLocked("test", 1000); - verifyLightStateConditions(LIGHT_STATE_ACTIVE); - enterLightState(LIGHT_STATE_IDLE); mDeviceIdleController.becomeActiveLocked("test", 1000); verifyLightStateConditions(LIGHT_STATE_ACTIVE); @@ -2059,7 +2164,7 @@ public class DeviceIdleControllerTest { while (mDeviceIdleController.getLightState() != lightState) { // Stepping through each state ensures that the proper features are turned // on/off. - mDeviceIdleController.stepLightIdleStateLocked("testing"); + mDeviceIdleController.stepLightIdleStateLocked("testing", true); count++; if (count > 10) { @@ -2068,7 +2173,6 @@ public class DeviceIdleControllerTest { } } break; - case LIGHT_STATE_PRE_IDLE: case LIGHT_STATE_WAITING_FOR_NETWORK: case LIGHT_STATE_OVERRIDE: setScreenOn(false); @@ -2213,7 +2317,6 @@ public class DeviceIdleControllerTest { > mAlarmManager.getNextWakeFromIdleTime()); break; case LIGHT_STATE_INACTIVE: - case LIGHT_STATE_PRE_IDLE: case LIGHT_STATE_IDLE: case LIGHT_STATE_WAITING_FOR_NETWORK: case LIGHT_STATE_IDLE_MAINTENANCE: diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 58854706f837..f05658bf6b0b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -108,6 +108,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; @@ -124,6 +125,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; import android.net.Uri; @@ -389,6 +391,7 @@ public class AlarmManagerServiceTest { .mockStatic(LocalServices.class) .spyStatic(Looper.class) .mockStatic(MetricsHelper.class) + .mockStatic(PermissionChecker.class) .mockStatic(PermissionManagerService.class) .mockStatic(ServiceManager.class) .mockStatic(Settings.Global.class) @@ -445,6 +448,10 @@ public class AlarmManagerServiceTest { doReturn(true) .when(() -> DateFormat.is24HourFormat(eq(mMockContext), anyInt())); + doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( + () -> PermissionChecker.checkPermissionForPreflight(any(), + eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString())); + when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); registerAppIds(new String[]{TEST_CALLING_PACKAGE}, @@ -2158,6 +2165,7 @@ public class AlarmManagerServiceTest { @Test public void canScheduleExactAlarmsBinderCall() throws RemoteException { + // Policy permission is denied in setUp(). mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // No permission, no exemption. @@ -2168,6 +2176,14 @@ public class AlarmManagerServiceTest { mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); + // Policy permission only, no exemption. + mockExactAlarmPermissionGrant(true, false, MODE_ERRORED); + doReturn(PermissionChecker.PERMISSION_GRANTED).when( + () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), + eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID), + eq(TEST_CALLING_PACKAGE))); + assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); + // Permission, no exemption. mockExactAlarmPermissionGrant(true, false, MODE_DEFAULT); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); @@ -2699,7 +2715,8 @@ public class AlarmManagerServiceTest { mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); // No permission revoked. - verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString()); + verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked(anyInt(), anyString(), + anyBoolean()); // Permission got granted only for (appId1, userId2). final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -2754,14 +2771,14 @@ public class AlarmManagerServiceTest { // Permission got revoked only for (appId1, userId2) verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId1, appId1)), eq(packages[0])); + eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId1, appId2)), eq(packages[1])); + eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); verify(mService, never()).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId2, appId2)), eq(packages[1])); + eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); verify(mService).removeExactAlarmsOnPermissionRevokedLocked( - eq(UserHandle.getUid(userId2, appId1)), eq(packages[0])); + eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); } @Test @@ -2774,7 +2791,7 @@ public class AlarmManagerServiceTest { mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); verify(mService).removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, - TEST_CALLING_PACKAGE); + TEST_CALLING_PACKAGE, true); } @Test @@ -2859,7 +2876,8 @@ public class AlarmManagerServiceTest { null); assertEquals(6, mService.mAlarmStore.size()); - mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE); + mService.removeExactAlarmsOnPermissionRevokedLocked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, + true); final ArrayList<Alarm> remaining = mService.mAlarmStore.asList(); assertEquals(3, remaining.size()); @@ -3080,7 +3098,7 @@ public class AlarmManagerServiceTest { SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED) - .setPackage(TEST_CALLING_PACKAGE) + .setData(Uri.fromParts("package", TEST_CALLING_PACKAGE, null)) .putExtra(Intent.EXTRA_REPLACING, true); mPackageChangesReceiver.onReceive(mMockContext, packageAdded); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java index 5746f6f2d446..e6acc904d811 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -88,27 +88,27 @@ public class BiometricContextProviderTest { } @Test - public void testIsAoD() throws RemoteException { + public void testIsAod() throws RemoteException { mListener.onDozeChanged(true); - assertThat(mProvider.isAoD()).isTrue(); + assertThat(mProvider.isAod()).isTrue(); mListener.onDozeChanged(false); - assertThat(mProvider.isAoD()).isFalse(); + assertThat(mProvider.isAod()).isFalse(); when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false); mListener.onDozeChanged(true); - assertThat(mProvider.isAoD()).isFalse(); + assertThat(mProvider.isAod()).isFalse(); mListener.onDozeChanged(false); - assertThat(mProvider.isAoD()).isFalse(); + assertThat(mProvider.isAod()).isFalse(); } @Test - public void testSubscribesToAoD() throws RemoteException { + public void testSubscribesToAod() throws RemoteException { final List<Boolean> expected = ImmutableList.of(true, false, true, true, false); final List<Boolean> actual = new ArrayList<>(); mProvider.subscribe(mOpContext, ctx -> { assertThat(ctx).isSameInstanceAs(mOpContext); - actual.add(ctx.isAoD); + actual.add(ctx.isAod); }); for (boolean v : expected) { @@ -178,7 +178,7 @@ public class BiometricContextProviderTest { assertThat(context).isSameInstanceAs(mOpContext); assertThat(mOpContext.id).isEqualTo(0); assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN); - assertThat(mOpContext.isAoD).isEqualTo(false); + assertThat(mOpContext.isAod).isEqualTo(false); assertThat(mOpContext.isCrypto).isEqualTo(false); for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT, @@ -192,7 +192,7 @@ public class BiometricContextProviderTest { assertThat(context).isSameInstanceAs(mOpContext); assertThat(mOpContext.id).isEqualTo(id); assertThat(mOpContext.reason).isEqualTo(reason(type)); - assertThat(mOpContext.isAoD).isEqualTo(aod); + assertThat(mOpContext.isAod).isEqualTo(aod); assertThat(mOpContext.isCrypto).isEqualTo(false); mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id)); @@ -202,7 +202,7 @@ public class BiometricContextProviderTest { assertThat(context).isSameInstanceAs(mOpContext); assertThat(mOpContext.id).isEqualTo(0); assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN); - assertThat(mOpContext.isAoD).isEqualTo(false); + assertThat(mOpContext.isAod).isEqualTo(false); assertThat(mOpContext.isCrypto).isEqualTo(false); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 3b4aece5997e..2ae285409b73 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.companion.virtual; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +40,7 @@ import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.audio.IAudioSessionCallback; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; @@ -111,6 +113,8 @@ public class VirtualDeviceManagerServiceTest { @Mock IThermalService mIThermalServiceMock; private PowerManager mPowerManager; + @Mock + private IAudioSessionCallback mCallback; @Before public void setUp() { @@ -250,6 +254,12 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onAudioSessionStarting_noDisplay_failsSecurityException() { + assertThrows(SecurityException.class, + () -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback)); + } + + @Test public void createVirtualKeyboard_noPermission_failsSecurityException() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( @@ -283,6 +293,22 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onAudioSessionStarting_noPermission_failsSecurityException() { + mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows(SecurityException.class, + () -> mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback)); + } + + @Test + public void onAudioSessionEnded_noPermission_failsSecurityException() { + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded()); + } + + @Test public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, @@ -316,6 +342,25 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void onAudioSessionStarting_hasVirtualAudioController() { + mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID); + + mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback); + + assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNotNull(); + } + + @Test + public void onAudioSessionEnded_noVirtualAudioController() { + mDeviceImpl.onVirtualDisplayCreatedLocked(DISPLAY_ID); + mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mCallback); + + mDeviceImpl.onAudioSessionEnded(); + + assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull(); + } + + @Test public void sendKeyEvent_noFd() { assertThrows( IllegalArgumentException.class, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java new file mode 100644 index 000000000000..3160272ef9b1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022 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.companion.virtual.audio; + +import static android.media.AudioAttributes.FLAG_SECURE; +import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.companion.virtual.audio.IAudioSessionCallback; +import android.content.Context; +import android.content.ContextWrapper; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; +import android.media.MediaRecorder; +import android.media.PlayerBase; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.ArraySet; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.companion.virtual.GenericWindowPolicyController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class VirtualAudioControllerTest { + private static final int APP1_UID = 100; + private static final int APP2_UID = 200; + + private Context mContext; + private VirtualAudioController mVirtualAudioController; + private GenericWindowPolicyController mGenericWindowPolicyController; + @Mock IAudioSessionCallback mCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); + mVirtualAudioController = new VirtualAudioController(mContext); + mGenericWindowPolicyController = new GenericWindowPolicyController( + FLAG_SECURE, + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, + /* allowedUsers= */ new ArraySet<>(), + /* allowedActivities= */ new ArraySet<>(), + /* blockedActivities= */ new ArraySet<>(), + /* activityListener= */null, + /* activityBlockedCallback= */ null); + } + + @Test + public void startListening_receivesCallback() throws RemoteException { + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + int[] appUids = new int[] {APP1_UID}; + + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + + mGenericWindowPolicyController.onRunningAppsChanged(runningUids); + verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void stopListening_removesCallback() throws RemoteException { + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + int[] appUids = new int[] {APP1_UID}; + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + + mVirtualAudioController.stopListening(); + + mGenericWindowPolicyController.onRunningAppsChanged(runningUids); + verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void onRunningAppsChanged_notifiesAudioRoutingModified() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + + int[] appUids = new int[] {APP1_UID}; + verify(mCallback).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void onRunningAppsChanged_audioIsPlaying_doesNothing() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + mVirtualAudioController.addPlayingAppsForTesting(APP2_UID); + + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + + int[] appUids = new int[]{APP1_UID}; + verify(mCallback, never()).onAppsNeedingAudioRoutingChanged(appUids); + } + + @Test + public void onRunningAppsChanged_lastPlayingAppRemoved_delaysReroutingAudio() { + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + runningUids.add(APP2_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + mVirtualAudioController.addPlayingAppsForTesting(APP2_UID); + + ArraySet<Integer> appUids = new ArraySet<>(); + appUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(appUids); + + assertThat(mVirtualAudioController.hasPendingRunnable()).isTrue(); + } + + @Test + public void onPlaybackConfigChanged_sendsCallback() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + List<AudioPlaybackConfiguration> configs = createPlaybackConfigurations(runningUids); + + mVirtualAudioController.onPlaybackConfigChanged(configs); + + verify(mCallback).onPlaybackConfigChanged(configs); + } + + @Test + public void onRecordingConfigChanged_sendsCallback() throws RemoteException { + mVirtualAudioController.startListening(mGenericWindowPolicyController, mCallback); + ArraySet<Integer> runningUids = new ArraySet<>(); + runningUids.add(APP1_UID); + mVirtualAudioController.onRunningAppsChanged(runningUids); + List<AudioRecordingConfiguration> configs = createRecordingConfigurations(runningUids); + + mVirtualAudioController.onRecordingConfigChanged(configs); + + verify(mCallback).onRecordingConfigChanged(configs); + } + + private List<AudioPlaybackConfiguration> createPlaybackConfigurations( + ArraySet<Integer> appUids) { + List<AudioPlaybackConfiguration> configs = new ArrayList<>(); + for (int appUid : appUids) { + PlayerBase.PlayerIdCard playerIdCard = + PlayerBase.PlayerIdCard.CREATOR.createFromParcel(Parcel.obtain()); + AudioPlaybackConfiguration audioPlaybackConfiguration = + new AudioPlaybackConfiguration( + playerIdCard, /* piid= */ 1000, appUid, /* pid= */ 1000); + audioPlaybackConfiguration.handleStateEvent(PLAYER_STATE_STARTED, /* deviceId= */1); + configs.add(audioPlaybackConfiguration); + } + return configs; + } + + private List<AudioRecordingConfiguration> createRecordingConfigurations( + ArraySet<Integer> appUids) { + List<AudioRecordingConfiguration> configs = new ArrayList<>(); + for (int appUid : appUids) { + AudioRecordingConfiguration audioRecordingConfiguration = + new AudioRecordingConfiguration( + /* uid= */ appUid, + /* session= */ 1000, + MediaRecorder.AudioSource.MIC, + /* clientFormat= */ null, + /* devFormat= */ null, + /* patchHandle= */ 1000, + "com.android.example"); + configs.add(audioRecordingConfiguration); + } + return configs; + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 67d69292b476..e7f4d3dccae7 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import android.app.PropertyInvalidatedCache; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.gatekeeper.GateKeeperResponse; @@ -47,6 +48,7 @@ import com.android.internal.widget.VerifyCredentialResponse; import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle; import com.android.server.locksettings.LockSettingsStorage.CredentialHash; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,6 +59,11 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(AndroidJUnit4.class) public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { + @Before + public void disableProcessCaches() { + PropertyInvalidatedCache.disableForTestMode(); + } + @Test public void testCreatePasswordPrimaryUser() throws RemoteException { testCreateCredential(PRIMARY_USER_ID, newPassword("password")); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java index 9c0239b2b6a6..c2f94e202ca3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java @@ -24,6 +24,7 @@ import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static org.junit.Assert.assertEquals; +import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; import androidx.test.runner.AndroidJUnit4; @@ -46,6 +47,11 @@ public class LockscreenFrpTest extends BaseLockSettingsServiceTests { mSettings.setDeviceProvisioned(false); } + @Before + public void disableProcessCaches() { + PropertyInvalidatedCache.disableForTestMode(); + } + @Test public void testFrpCredential_setPin() { mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index c0a38b874914..58e1c4378c41 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.admin.PasswordMetrics; +import android.app.PropertyInvalidatedCache; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; @@ -49,6 +50,7 @@ import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationRe import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; import com.android.server.locksettings.SyntheticPasswordManager.PasswordData; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -68,6 +70,11 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 55}; public static final byte[] PAYLOAD2 = new byte[] {2, 3, -2, -3, 44, 1}; + @Before + public void disableProcessCaches() { + PropertyInvalidatedCache.disableForTestMode(); + } + @Test public void testPasswordBasedSyntheticPassword() throws RemoteException { final int USER_ID = 10; diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 827349ad433a..c94168c59fde 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -87,6 +87,7 @@ import com.android.server.lights.LightsManager; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.PowerManagerService.BatteryReceiver; import com.android.server.power.PowerManagerService.BinderService; +import com.android.server.power.PowerManagerService.DockReceiver; import com.android.server.power.PowerManagerService.Injector; import com.android.server.power.PowerManagerService.NativeWrapper; import com.android.server.power.PowerManagerService.UserSwitchedReceiver; @@ -152,6 +153,7 @@ public class PowerManagerServiceTest { private Resources mResourcesSpy; private OffsettableClock mClock; private TestLooper mTestLooper; + private DockReceiver mDockReceiver; private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> { private final IntentFilter mFilter; @@ -337,6 +339,14 @@ public class PowerManagerServiceTest { argThat(new IntentFilterMatcher(usFilter)), isNull(), isA(Handler.class)); mUserSwitchedReceiver = userSwitchedCaptor.getValue(); + // Grab the DockReceiver + ArgumentCaptor<DockReceiver> dockReceiverCaptor = + ArgumentCaptor.forClass(DockReceiver.class); + IntentFilter dockFilter = new IntentFilter(Intent.ACTION_DOCK_EVENT); + verify(mContextSpy).registerReceiver(dockReceiverCaptor.capture(), + argThat(new IntentFilterMatcher(dockFilter)), isNull(), isA(Handler.class)); + mDockReceiver = dockReceiverCaptor.getValue(); + mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); } @@ -385,6 +395,16 @@ public class PowerManagerServiceTest { .thenReturn(minimumScreenOffTimeoutConfigMillis); } + private void setScreenOffTimeout(int screenOffTimeoutMillis) { + Settings.System.putInt(mContextSpy.getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT, + screenOffTimeoutMillis); + } + + private void setScreenOffTimeoutDocked(int screenOffTimeoutMillis) { + Settings.System.putInt(mContextSpy.getContentResolver(), + Settings.System.SCREEN_OFF_TIMEOUT_DOCKED, screenOffTimeoutMillis); + } + private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); @@ -883,6 +903,71 @@ public class PowerManagerServiceTest { } @Test + public void testScreenOffTimeout_goesToSleepAfterTimeout() { + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); + + setMinimumScreenOffTimeoutConfig(10); + setScreenOffTimeout(10); + + createService(); + startSystem(); + + mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + advanceTime(15); + assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_AWAKE); + } + + @Test + public void testScreenOffTimeout_usesRegularTimeoutWhenNotDocked() { + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); + + setMinimumScreenOffTimeoutConfig(10); + setScreenOffTimeout(10); + setScreenOffTimeoutDocked(30); + + createService(); + startSystem(); + + mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + advanceTime(15); + assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_AWAKE); + } + + @Test + public void testScreenOffTimeout_usesDockedTimeoutWhenDocked() { + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info); + + setMinimumScreenOffTimeoutConfig(10); + setScreenOffTimeout(10); + setScreenOffTimeoutDocked(30); + + createService(); + startSystem(); + + mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(), + PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); + mDockReceiver.onReceive(mContextSpy, + new Intent(Intent.ACTION_DOCK_EVENT).putExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_DESK)); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + advanceTime(15); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + advanceTime(20); + assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_AWAKE); + } + + @Test public void testInattentiveSleep_goesToSleepWithWakeLock() { final String pkg = mContextSpy.getOpPackageName(); final Binder token = new Binder(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 511296ade63f..ef9494aca4a5 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4659,6 +4659,59 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + + // Set up notifications: r1 is adjusted, r2 is not + final NotificationRecord r1 = generateNotificationRecord( + mTestNotificationChannel, 1, null, true); + r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); + mService.addNotification(r1); + final NotificationRecord r2 = generateNotificationRecord( + mTestNotificationChannel, 2, null, true); + r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); + mService.addNotification(r2); + + // Test an adjustment that sets importance to none (meaning it's cancelling) + Bundle signals1 = new Bundle(); + signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_NONE); + Adjustment adjustment1 = new Adjustment( + r1.getSbn().getPackageName(), r1.getKey(), signals1, "", + r1.getUser().getIdentifier()); + + mBinderService.applyAdjustmentFromAssistant(null, adjustment1); + + // Actually apply the adjustments & recalculate importance when run + doAnswer(invocationOnMock -> { + ((NotificationRecord) invocationOnMock.getArguments()[0]) + .applyAdjustments(); + ((NotificationRecord) invocationOnMock.getArguments()[0]) + .calculateImportance(); + return null; + }).when(mRankingHelper).extractSignals(any(NotificationRecord.class)); + + // run the CancelNotificationRunnable when it happens + ArgumentCaptor<NotificationManagerService.CancelNotificationRunnable> captor = + ArgumentCaptor.forClass( + NotificationManagerService.CancelNotificationRunnable.class); + + verify(handler, times(1)).scheduleCancelNotification( + captor.capture()); + + // Run the runnable given to the cancel notification, and see if it logs properly + NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue(); + runnable.run(); + assertEquals(1, mNotificationRecordLogger.numCalls()); + assertEquals( + NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT, + mNotificationRecordLogger.event(0)); + } + + @Test public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addEnqueuedNotification(r); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 3298d1184cdc..043bc0700ab4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -121,6 +121,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.PersistableBundle; @@ -3358,6 +3359,29 @@ public class ActivityRecordTests extends WindowTestsBase { noProcActivity.mInputDispatchingTimeoutMillis); } + @Test + public void testEnsureActivitiesVisibleAnotherUserTasks() { + // Create an activity with hierarchy: + // RootTask + // - TaskFragment + // - Activity + DisplayContent display = createNewDisplay(); + Task rootTask = createTask(display); + ActivityRecord activity = createActivityRecord(rootTask); + final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(), + true /* createdByOrganizer */, true /* isEmbedded */); + activity.getTask().addChild(taskFragment, POSITION_TOP); + activity.reparent(taskFragment, POSITION_TOP); + + // Ensure the activity visibility is updated even it is not shown to current user. + activity.mVisibleRequested = true; + doReturn(false).when(activity).showToCurrentUser(); + spyOn(taskFragment); + doReturn(false).when(taskFragment).shouldBeVisible(any()); + display.ensureActivitiesVisible(null, 0, false, false); + assertFalse(activity.mVisibleRequested); + } + private ICompatCameraControlCallback getCompatCameraControlCallback() { return new ICompatCameraControlCallback.Stub() { @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index efc9a49023b1..9f7130e45483 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -447,25 +447,6 @@ public class DisplayContentTests extends WindowTestsBase { } /** - * This tests override configuration updates for display content. - */ - @Test - public void testDisplayOverrideConfigUpdate() { - final Configuration currentOverrideConfig = - mDisplayContent.getRequestedOverrideConfiguration(); - - // Create new, slightly changed override configuration and apply it to the display. - final Configuration newOverrideConfig = new Configuration(currentOverrideConfig); - newOverrideConfig.densityDpi += 120; - newOverrideConfig.fontScale += 0.3; - - mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, mDisplayContent); - - // Check that override config is applied. - assertEquals(newOverrideConfig, mDisplayContent.getRequestedOverrideConfiguration()); - } - - /** * This tests global configuration updates when default display config is updated. */ @Test @@ -478,7 +459,8 @@ public class DisplayContentTests extends WindowTestsBase { newOverrideConfig.densityDpi += 120; newOverrideConfig.fontScale += 0.3; - mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, defaultDisplay); + defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig, + null /* starting */, false /* deferResume */, null /* result */); // Check that global configuration is updated, as we've updated default display's config. Configuration globalConfig = mWm.mRoot.getConfiguration(); @@ -486,7 +468,8 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); // Return back to original values. - mWm.setNewDisplayOverrideConfiguration(currentConfig, defaultDisplay); + defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig, + null /* starting */, false /* deferResume */, null /* result */); globalConfig = mWm.mRoot.getConfiguration(); assertEquals(currentConfig.densityDpi, globalConfig.densityDpi); assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 87f76fab3bf8..e0911909f47e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -632,10 +629,11 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { @Test public void testAttachNavBarInSplitScreenMode() { setupForShouldAttachNavBarDuringTransition(); - final ActivityRecord primary = createActivityRecordWithParentTask(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); - final ActivityRecord secondary = createActivityRecordWithParentTask(mDefaultDisplay, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD); + TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); + final ActivityRecord primary = createActivityRecordWithParentTask( + organizer.createTaskToPrimary(true)); + final ActivityRecord secondary = createActivityRecordWithParentTask( + organizer.createTaskToSecondary(true)); final ActivityRecord homeActivity = createHomeActivity(); homeActivity.setVisibility(true); initializeRecentsAnimationController(mController, homeActivity); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index f573b70352af..e4336843a35b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -116,7 +116,7 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { public void testGetSnapshotMode() { final WindowState disabledWindow = createWindow(null, FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow"); - disabledWindow.mActivityRecord.setDisablePreviewScreenshots(true); + disabledWindow.mActivityRecord.setRecentsScreenshotEnabled(false); assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask())); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 409572847623..05eedcf21d4f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.os.Process.SYSTEM_UID; import static android.view.View.VISIBLE; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; @@ -1531,7 +1532,11 @@ class WindowTestsBase extends SystemServiceTestsBase { final Rect primaryBounds = new Rect(); final Rect secondaryBounds = new Rect(); - display.getBounds().splitVertically(primaryBounds, secondaryBounds); + if (display.getConfiguration().orientation == ORIENTATION_LANDSCAPE) { + display.getBounds().splitVertically(primaryBounds, secondaryBounds); + } else { + display.getBounds().splitHorizontally(primaryBounds, secondaryBounds); + } mPrimary.setBounds(primaryBounds); mSecondary.setBounds(secondaryBounds); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 8acd3c7c1c36..84bd98347dab 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -45,7 +45,6 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; @@ -278,8 +277,10 @@ final class HotwordDetectionConnection { mRemoteHotwordDetectionService.unbind(); LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(null); + if (mIdentity != null) { + removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid()); + } mIdentity = null; - updateServiceUidForAudioPolicy(Process.INVALID_UID); mCancellationTaskFuture.cancel(/* may interrupt */ true); if (mAudioFlinger != null) { mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); @@ -909,17 +910,27 @@ final class HotwordDetectionConnection { LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); - updateServiceUidForAudioPolicy(uid); + addServiceUidForAudioPolicy(uid); } })); } - private void updateServiceUidForAudioPolicy(int uid) { + private void addServiceUidForAudioPolicy(int uid) { + mScheduledExecutorService.execute(() -> { + AudioManagerInternal audioManager = + LocalServices.getService(AudioManagerInternal.class); + if (audioManager != null) { + audioManager.addAssistantServiceUid(uid); + } + }); + } + + private void removeServiceUidForAudioPolicy(int uid) { mScheduledExecutorService.execute(() -> { - final AudioManagerInternal audioManager = + AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); if (audioManager != null) { - audioManager.setHotwordDetectionServiceUid(uid); + audioManager.removeAssistantServiceUid(uid); } }); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a1e37dbabc8c..03be19f40fcb 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5219,7 +5219,7 @@ public class CarrierConfigManager { * <li>{@link #KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY}</li> * <li>{@link #KEY_CAPABILITY_TYPE_UT_INT_ARRAY}</li> * <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li> - * <li>{@link #KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY}</li> * </ul> * <p> The values are defined in * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech} @@ -5228,39 +5228,68 @@ public class CarrierConfigManager { KEY_PREFIX + "mmtel_requires_provisioning_bundle"; /** - * This MmTelFeature supports Voice calling (IR.92) + * List of different RAT technologies on which Provisioning for Voice calling (IR.92) + * is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = - KEY_PREFIX + "key_capability_type_voice_int_array"; + KEY_PREFIX + "capability_type_voice_int_array"; /** - * This MmTelFeature supports Video (IR.94) + * List of different RAT technologies on which Provisioning for Video Telephony (IR.94) + * is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = - KEY_PREFIX + "key_capability_type_video_int_array"; + KEY_PREFIX + "capability_type_video_int_array"; /** - * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92) + * List of different RAT technologies on which Provisioning for XCAP over Ut for + * supplementary services. (IR.92) is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = - KEY_PREFIX + "key_capability_type_ut_int_array"; + KEY_PREFIX + "capability_type_ut_int_array"; /** - * This MmTelFeature supports SMS (IR.92) + * List of different RAT technologies on which Provisioning for SMS (IR.92) is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = - KEY_PREFIX + "key_capability_type_sms_int_array"; + KEY_PREFIX + "capability_type_sms_int_array"; /** - * This MmTelFeature supports Call Composer (section 2.4 of RCC.20) + * List of different RAT technologies on which Provisioning for Call Composer + * (section 2.4 of RCC.20) is supported. * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ - public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = - KEY_PREFIX + "key_capability_type_call_composer_int_array"; + public static final String KEY_CAPABILITY_TYPE_CALL_COMPOSER_INT_ARRAY = + KEY_PREFIX + "capability_type_call_composer_int_array"; /** * A bundle which specifies the RCS capability and registration technology @@ -5283,9 +5312,14 @@ public class CarrierConfigManager { * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. * If not set, this RcsFeature should not service capability requests. * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = - KEY_PREFIX + "key_capability_type_options_uce_int_array"; + KEY_PREFIX + "capability_type_options_uce_int_array"; /** * This carrier supports User Capability Exchange using a presence server as defined by the @@ -5293,9 +5327,14 @@ public class CarrierConfigManager { * server. If not set, this RcsFeature should not publish capabilities or service capability * requests using presence. * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE + * <p>Possible values are, + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_LTE} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_IWLAN} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_CROSS_SIM} + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationImplBase.ImsRegistrationTech#REGISTRATION_TECH_NR} */ public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = - KEY_PREFIX + "key_capability_type_presence_uce_int_array"; + KEY_PREFIX + "capability_type_presence_uce_int_array"; private Ims() {} @@ -5337,16 +5376,13 @@ public class CarrierConfigManager { /** * @see #KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE */ - PersistableBundle mmtel_requires_provisioning_int_array = new PersistableBundle(); defaults.putPersistableBundle( - KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, mmtel_requires_provisioning_int_array); - + KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle()); /** * @see #KEY_RCS_REQUIRES_PROVISIONING_BUNDLE */ - PersistableBundle rcs_requires_provisioning_int_array = new PersistableBundle(); defaults.putPersistableBundle( - KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, rcs_requires_provisioning_int_array); + KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, new PersistableBundle()); defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true); defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ba1a6edd74ad..613b0a667612 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7118,7 +7118,14 @@ public class TelephonyManager { */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) public boolean iccCloseLogicalChannel(int channel) { - return iccCloseLogicalChannel(getSubId(), channel); + try { + return iccCloseLogicalChannel(getSubId(), channel); + } catch (IllegalStateException ex) { + Rlog.e(TAG, "iccCloseLogicalChannel IllegalStateException", ex); + } catch (IllegalArgumentException ex) { + Rlog.e(TAG, "iccCloseLogicalChannel IllegalArgumentException", ex); + } + return false; } /** @@ -7639,7 +7646,7 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). * - * TODO: remove this one. use {@link #rebootRadio()} for reset type 1 and + * TODO: remove this one. use {@link #rebootModem()} for reset type 1 and * {@link #resetRadioConfig()} for reset type 3 * * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset @@ -7706,6 +7713,8 @@ public class TelephonyManager { * * @return {@code true} on success; {@code false} on any failure. * + * @deprecated Using {@link #rebootModem()} instead. + * * @hide */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @@ -7726,6 +7735,30 @@ public class TelephonyManager { } /** + * Generate a radio modem reset. Used for device configuration by some carriers. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws RuntimeException + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) + public void rebootModem() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + throw new IllegalStateException("telephony service is null."); + } + telephony.rebootModem(getSlotIndex()); + } catch (RemoteException ex) { + Rlog.e(TAG, "rebootRadio RemoteException", ex); + throw ex.rethrowAsRuntimeException(); + } + } + + /** * Return an appropriate subscription ID for any situation. * * If this object has been created with {@link #createForSubscriptionId}, then the provided diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index f614988d1950..0a2bb3d16f24 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -118,6 +118,9 @@ public class EuiccCardManager { /** Resets the default SM-DP+ address. */ public static final int RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS = 1 << 2; + /** Result code when the requested profile is not found */ + public static final int RESULT_PROFILE_NOT_FOUND = 1; + /** Result code of execution with no error. */ public static final int RESULT_OK = 0; @@ -130,9 +133,6 @@ public class EuiccCardManager { /** Result code indicating the caller is not the active LPA. */ public static final int RESULT_CALLER_NOT_ALLOWED = -3; - /** Result code when the requested profile is not found */ - public static final int RESULT_PROFILE_NOT_FOUND = -4; - /** * Callback to receive the result of an eUICC card API. * @@ -223,7 +223,9 @@ public class EuiccCardManager { } /** - * Requests the enabled profile for a given port on an eUicc. + * Requests the enabled profile for a given port on an eUicc. Callback with result code + * {@link RESULT_PROFILE_NOT_FOUND} and {@code NULL} EuiccProfile if there is no enabled + * profile on the target port. * * @param cardId The Id of the eUICC. * @param portIndex The portIndex to use. The port may be active or inactive. As long as the diff --git a/test-base/Android.bp b/test-base/Android.bp index 8be732452228..527159a78ebf 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -72,11 +72,16 @@ java_sdk_library { // Build the android.test.base_static library // ========================================== -// This is only intended for inclusion in the android.test.runner-minus-junit, -// robolectric_android-all-stub and repackaged.android.test.* libraries. +// This is only intended for use by the android.test.runner-minus-junit +// library. +// // Must not be used elsewhere. +// java_library_static { name: "android.test.base_static", + visibility: [ + "//frameworks/base/test-runner", + ], installable: false, srcs: [":android-test-base-sources"], @@ -91,28 +96,10 @@ java_library_static { sdk_version: "current", } -// Build the repackaged.android.test.base library -// ============================================== -// This contains repackaged versions of the classes from -// android.test.base. -java_library_static { - name: "repackaged.android.test.base", - - sdk_version: "current", - static_libs: ["android.test.base_static"], - - jarjar_rules: "jarjar-rules.txt", - // Pin java_version until jarjar is certified to support later versions. http://b/72703434 - java_version: "1.8", -} - // Build the android.test.base-minus-junit library // =============================================== // This contains the android.test classes from android.test.base plus -// the com.android.internal.util.Predicate[s] classes. This is only -// intended for inclusion in android.test.legacy and in -// android.test.base-hiddenapi-annotations to avoid a dependency cycle and must -// not be used elsewhere. +// the com.android.internal.util.Predicate[s] classes. java_library_static { name: "android.test.base-minus-junit", diff --git a/test-base/jarjar-rules.txt b/test-base/jarjar-rules.txt deleted file mode 100644 index fd8555c8931c..000000000000 --- a/test-base/jarjar-rules.txt +++ /dev/null @@ -1,3 +0,0 @@ -rule junit.** repackaged.junit.@1 -rule android.test.** repackaged.android.test.@1 -rule com.android.internal.util.** repackaged.com.android.internal.util.@1 diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 2a19af9f8cd2..13a5dac9eb38 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -79,32 +79,6 @@ java_library { ], } -// Build the repackaged.android.test.runner library -// ================================================ -java_library_static { - name: "repackaged.android.test.runner", - - srcs: [":android-test-runner-sources"], - exclude_srcs: [ - "src/android/test/ActivityUnitTestCase.java", - "src/android/test/ApplicationTestCase.java", - "src/android/test/IsolatedContext.java", - "src/android/test/ProviderTestCase.java", - "src/android/test/ProviderTestCase2.java", - "src/android/test/RenamingDelegatingContext.java", - "src/android/test/ServiceTestCase.java", - ], - - sdk_version: "current", - libs: [ - "android.test.base_static", - ], - - jarjar_rules: "jarjar-rules.txt", - // Pin java_version until jarjar is certified to support later versions. http://b/72703434 - java_version: "1.8", -} - // Make the current.txt available for use by the cts/tests/signature tests. // ======================================================================== filegroup { diff --git a/test-runner/jarjar-rules.txt b/test-runner/jarjar-rules.txt deleted file mode 120000 index f6f79139d511..000000000000 --- a/test-runner/jarjar-rules.txt +++ /dev/null @@ -1 +0,0 @@ -../test-base/jarjar-rules.txt
\ No newline at end of file diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java index fa5724ea64bc..ca909a4c9b77 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java @@ -30,7 +30,7 @@ public class DisableScreenshotsActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setDisablePreviewScreenshots(true); + setRecentsScreenshotEnabled(false); getWindow().getDecorView().setBackgroundColor(Color.RED); } diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java deleted file mode 100644 index 6266cda204b0..000000000000 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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.internal.os; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.BatteryStats; -import android.os.Bundle; -import android.os.UserHandle; -import android.perftests.utils.BenchmarkState; -import android.perftests.utils.PerfStatusReporter; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class BatteryStatsHelperPerfTest { - - @Rule - public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - - /** - * Measures the performance of {@link BatteryStatsHelper#getStats()}, which triggers - * a battery stats sync on every iteration. - */ - @Test - public void testGetStats_forceUpdate() { - final Context context = InstrumentationRegistry.getContext(); - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - state.pauseTiming(); - statsHelper.clearStats(); - state.resumeTiming(); - - statsHelper.getStats(); - - assertThat(statsHelper.getUsageList()).isNotEmpty(); - } - } - - /** - * Measures performance of the {@link BatteryStatsHelper#getStats(boolean)}, which does - * not trigger a sync and just returns current values. - */ - @Test - public void testGetStats_cached() { - final Context context = InstrumentationRegistry.getContext(); - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - state.pauseTiming(); - statsHelper.clearStats(); - state.resumeTiming(); - - statsHelper.getStats(false /* forceUpdate */); - - assertThat(statsHelper.getUsageList()).isNotEmpty(); - } - } - - @Test - public void testPowerCalculation() { - final Context context = InstrumentationRegistry.getContext(); - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.getStats(); - - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - // This will use the cached BatteryStatsObject - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - assertThat(statsHelper.getUsageList()).isNotEmpty(); - } - } - - @Test - public void testEndToEnd() { - final Context context = InstrumentationRegistry.getContext(); - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - while (state.keepRunning()) { - final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, - true /* collectBatteryBroadcast */); - statsHelper.create((Bundle) null); - statsHelper.clearStats(); - statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId()); - - state.pauseTiming(); - - List<BatterySipper> usageList = statsHelper.getUsageList(); - double power = 0; - for (int i = 0; i < usageList.size(); i++) { - BatterySipper sipper = usageList.get(i); - power += sipper.sumPower(); - } - - assertThat(power).isGreaterThan(0.0); - - state.resumeTiming(); - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 294a220d6d4d..a425ee0ed969 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -142,7 +142,7 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 519bd5627b28..a0892612f4e4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -157,7 +157,7 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index ca73503164e6..8d60466eff95 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -18,7 +18,6 @@ package com.android.server.wm.flicker.close import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -194,22 +193,4 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) open fun launcherLayerReplacesApp() { testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } - - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index c87d8e1b98e1..dd5f33fb8669 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -170,7 +170,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5, + .getConfigNonRotationTests(repetitions = 3, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 815ea778555b..5606965a6d40 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -178,7 +178,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5, + .getConfigNonRotationTests(repetitions = 3, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 24b1598f899c..e7a1c50821b7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -160,7 +160,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index e5d82a11c389..b454f0155b3e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -164,7 +164,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt index 005c4f59de50..964390930d54 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -140,7 +140,7 @@ class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index 87f8ef28cfda..8fcb4b7c03f1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -152,7 +152,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 5e06f11fb6b8..5f0176e76e36 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -238,7 +238,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedRotations = listOf(Surface.ROTATION_0) ) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index 195af589d77c..b5e13be7dca0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -152,7 +152,7 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 3a2eba1d851b..797919b03726 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -130,7 +130,7 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart() /** {@inheritDoc} */ - @Presubmit + @FlakyTest(bugId = 218624176) @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() @@ -149,6 +149,22 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) super.appWindowReplacesLauncherAsTopWindow() } + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + assumeFalse(isShellTransitionsEnabled) + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 218470989) + @Test + fun visibleWindowsShownMoreThanOneConsecutiveEntry_shellTransit() { + assumeTrue(isShellTransitionsEnabled) + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + companion object { /** * Creates the test configurations. @@ -160,7 +176,7 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 6365e7b9e967..f75c50eab077 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -160,7 +160,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY), supportedRotations = listOf(Surface.ROTATION_0) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 882e128fe181..04fdda425edf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -244,7 +244,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 3) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index 0a64939cb4ca..5301e0275b53 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -318,7 +318,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 42941c2a914c..ce6a3837ad01 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -357,7 +357,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index 8f2803e97986..1a762bfd97c5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -333,7 +333,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 5, + repetitions = 3, supportedNavigationModes = listOf( WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY ), diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 3f0de7f3cd7d..f603f6e7ed9d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -25,11 +25,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -94,24 +96,6 @@ class ChangeAppRotationTest( } } - @FlakyTest - @Test - fun runPresubmitAssertion() { - flickerRule.checkPresubmitAssertions() - } - - @FlakyTest - @Test - fun runPostsubmitAssertion() { - flickerRule.checkPostsubmitAssertions() - } - - @FlakyTest - @Test - fun runFlakyAssertion() { - flickerRule.checkFlakyAssertions() - } - /** * Windows maybe recreated when rotated. Checks that the focus does not change or if it does, * focus returns to [testApp] @@ -131,6 +115,7 @@ class ChangeAppRotationTest( @Presubmit @Test fun rotationLayerAppearsAndVanishes() { + Assume.assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(testApp.component) .then() @@ -141,6 +126,13 @@ class ChangeAppRotationTest( } } + @FlakyTest(bugId = 218484127) + @Test + fun rotationLayerAppearsAndVanishes_shellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + rotationLayerAppearsAndVanishes() + } + /** * Checks that the status bar window is visible and above the app windows in all WM * trace entries @@ -191,7 +183,7 @@ class ChangeAppRotationTest( @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigRotationTests(repetitions = 5) + .getConfigRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/HandwritingIme/Android.bp b/tests/HandwritingIme/Android.bp new file mode 100644 index 000000000000..1f552bf4dc6d --- /dev/null +++ b/tests/HandwritingIme/Android.bp @@ -0,0 +1,35 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "HandwritingIme", + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + static_libs: [ + "androidx.core_core", + "androidx.appcompat_appcompat", + "com.google.android.material_material", + ], +} diff --git a/tests/HandwritingIme/AndroidManifest.xml b/tests/HandwritingIme/AndroidManifest.xml new file mode 100644 index 000000000000..1445d95c2879 --- /dev/null +++ b/tests/HandwritingIme/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (018C) 2022 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 + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.test.handwritingime"> + + <application android:label="Handwriting IME"> + <service android:name=".HandwritingIme" + android:process=":HandwritingIme" + android:label="Handwriting IME" + android:permission="android.permission.BIND_INPUT_METHOD" + android:exported="true"> + <intent-filter> + <action android:name="android.view.InputMethod"/> + </intent-filter> + <meta-data android:name="android.view.im" + android:resource="@xml/ime"/> + </service> + + </application> +</manifest> diff --git a/tests/HandwritingIme/res/xml/ime.xml b/tests/HandwritingIme/res/xml/ime.xml new file mode 100644 index 000000000000..2e84a0389429 --- /dev/null +++ b/tests/HandwritingIme/res/xml/ime.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> + +<!-- Configuration info for an input method --> +<input-method xmlns:android="http://schemas.android.com/apk/res/android" + android:supportsStylusHandwriting="true"/>
\ No newline at end of file diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java new file mode 100644 index 000000000000..18f962398fb8 --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 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.google.android.test.handwritingime; + +import android.annotation.Nullable; +import android.inputmethodservice.InputMethodService; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.Toast; + +import java.util.Random; + +public class HandwritingIme extends InputMethodService { + + public static final int HEIGHT_DP = 100; + + private Window mInkWindow; + private InkView mInk; + + static final String TAG = "HandwritingIme"; + + interface HandwritingFinisher { + void finish(); + } + + interface StylusListener { + void onStylusEvent(MotionEvent me); + } + + final class StylusConsumer implements StylusListener { + @Override + public void onStylusEvent(MotionEvent me) { + HandwritingIme.this.onStylusEvent(me); + } + } + + final class HandwritingFinisherImpl implements HandwritingFinisher { + + HandwritingFinisherImpl() {} + + @Override + public void finish() { + finishStylusHandwriting(); + Log.d(TAG, "HandwritingIme called finishStylusHandwriting() "); + } + } + + private void onStylusEvent(@Nullable MotionEvent event) { + // TODO Hookup recognizer here + if (event.getAction() == MotionEvent.ACTION_UP) { + sendKeyChar((char) (56 + new Random().nextInt(66))); + } + } + + @Override + public View onCreateInputView() { + Log.d(TAG, "onCreateInputView"); + final ViewGroup view = new FrameLayout(this); + final View inner = new View(this); + final float density = getResources().getDisplayMetrics().density; + final int height = (int) (HEIGHT_DP * density); + view.setPadding(0, 0, 0, 0); + view.addView(inner, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, height)); + inner.setBackgroundColor(0xff0110fe); // blue + + return view; + } + + public void onPrepareStylusHandwriting() { + Log.d(TAG, "onPrepareStylusHandwriting "); + if (mInk == null) { + mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer()); + } + } + + @Override + public boolean onStartStylusHandwriting() { + Log.d(TAG, "onStartStylusHandwriting "); + Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show(); + mInkWindow = getStylusHandwritingWindow(); + mInkWindow.setContentView(mInk, mInk.getLayoutParams()); + return true; + } + + @Override + public void onFinishStylusHandwriting() { + Log.d(TAG, "onFinishStylusHandwriting "); + Toast.makeText(this, "Finish HW", Toast.LENGTH_SHORT).show(); + // Free-up + ((ViewGroup) mInk.getParent()).removeView(mInk); + mInk = null; + } +} diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java new file mode 100644 index 000000000000..4ffdc9211f1d --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 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.google.android.test.handwritingime; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +class InkView extends View { + private static final long FINISH_TIMEOUT = 2500; + private final HandwritingIme.HandwritingFinisher mHwCanceller; + private final HandwritingIme.StylusConsumer mConsumer; + private Paint mPaint; + private Path mPath; + private float mX, mY; + private static final float STYLUS_MOVE_TOLERANCE = 1; + private Runnable mFinishRunnable; + + InkView(Context context, HandwritingIme.HandwritingFinisher hwController, + HandwritingIme.StylusConsumer consumer) { + super(context); + mHwCanceller = hwController; + mConsumer = consumer; + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + mPaint.setColor(Color.GREEN); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeJoin(Paint.Join.ROUND); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStrokeWidth(14); + + mPath = new Path(); + + WindowManager wm = context.getSystemService(WindowManager.class); + WindowMetrics metrics = wm.getCurrentWindowMetrics(); + Insets insets = metrics.getWindowInsets() + .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + setLayoutParams(new ViewGroup.LayoutParams( + metrics.getBounds().width() - insets.left - insets.right, + metrics.getBounds().height() - insets.top - insets.bottom)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawPath(mPath, mPaint); + canvas.drawARGB(20, 255, 50, 50); + } + + private void stylusStart(float x, float y) { + mPath.moveTo(x, y); + mX = x; + mY = y; + } + + private void stylusMove(float x, float y) { + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (mPath.isEmpty()) { + stylusStart(x, y); + } + if (dx >= STYLUS_MOVE_TOLERANCE || dy >= STYLUS_MOVE_TOLERANCE) { + mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + } + + private void stylusFinish() { + mPath.lineTo(mX, mY); + // TODO: support offscreen? e.g. mCanvas.drawPath(mPath, mPaint); + mPath.reset(); + mX = 0; + mY = 0; + + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) { + mConsumer.onStylusEvent(event); + android.util.Log.w(HandwritingIme.TAG, "INK touch onStylusEvent " + event); + float x = event.getX(); + float y = event.getY(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + cancelTimer(); + stylusStart(x, y); + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + stylusMove(x, y); + invalidate(); + break; + + case MotionEvent.ACTION_UP: + scheduleTimer(); + break; + + } + return true; + } + return false; + } + + private void cancelTimer() { + if (mFinishRunnable != null) { + if (getHandler() != null) { + getHandler().removeCallbacks(mFinishRunnable); + } + mFinishRunnable = null; + } + if (getHandler() != null) { + getHandler().removeCallbacksAndMessages(null); + } + } + + private void scheduleTimer() { + cancelTimer(); + if (getHandler() != null) { + postDelayed(getFinishRunnable(), FINISH_TIMEOUT); + } + } + + private Runnable getFinishRunnable() { + mFinishRunnable = () -> { + android.util.Log.e(HandwritingIme.TAG, "Hw view timer finishHandwriting "); + mHwCanceller.finish(); + stylusFinish(); + mPath.reset(); + invalidate(); + }; + + return mFinishRunnable; + } + +} diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 824f91e1e826..15a6afc5ff7c 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -62,6 +62,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.view.PendingInsetsControllerTest", "android.window.", // all tests under the package. "android.app.activity.ActivityThreadTest", + "android.app.activity.RegisterComponentCallbacksTest" }; public FrameworksTestsFilter(Bundle testArgs) { |