Merge "Update Nullability of Apis"
diff --git a/api/current.txt b/api/current.txt
index 3f09d35..54814c7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30556,7 +30556,7 @@
}
public static interface WifiP2pManager.DeviceInfoListener {
- method public void onDeviceInfoAvailable(android.net.wifi.p2p.WifiP2pDevice);
+ method public void onDeviceInfoAvailable(@Nullable android.net.wifi.p2p.WifiP2pDevice);
}
public static interface WifiP2pManager.DiscoveryStateListener {
@@ -43830,7 +43830,6 @@
}
public final class PhoneAccountSuggestion implements android.os.Parcelable {
- ctor public PhoneAccountSuggestion(@NonNull android.telecom.PhoneAccountHandle, int, boolean);
method public int describeContents();
method @NonNull public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
method public int getReason();
diff --git a/api/system-current.txt b/api/system-current.txt
index b658640..b831785 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7002,6 +7002,10 @@
field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
}
+ public final class PhoneAccountSuggestion implements android.os.Parcelable {
+ ctor public PhoneAccountSuggestion(@NonNull android.telecom.PhoneAccountHandle, int, boolean);
+ }
+
public class PhoneAccountSuggestionService extends android.app.Service {
ctor public PhoneAccountSuggestionService();
method public void onAccountSuggestionRequest(@NonNull String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 6f8e0fa..b220325 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2355,6 +2355,10 @@
method public android.telecom.Connection getPrimaryConnection();
}
+ public final class PhoneAccountSuggestion implements android.os.Parcelable {
+ ctor public PhoneAccountSuggestion(@NonNull android.telecom.PhoneAccountHandle, int, boolean);
+ }
+
public class PhoneAccountSuggestionService extends android.app.Service {
ctor public PhoneAccountSuggestionService();
method public void onAccountSuggestionRequest(@NonNull String);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 92db23b..664f0a3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -196,6 +196,9 @@
/** Kill the processes in the list due to their tasks been removed. */
public abstract void killProcessesForRemovedTask(ArrayList<Object> procsToKill);
+ /** Kill the process immediately. */
+ public abstract void killProcess(String processName, int uid, String reason);
+
/**
* Returns {@code true} if {@code uid} is running an activity from {@code packageName}.
*/
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index b16188f..a6b76cb 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -473,4 +473,14 @@
* contain one task.
*/
void setDisplayToSingleTaskInstance(int displayId);
+
+ /**
+ * Restarts the activity by killing its process if it is visible. If the activity is not
+ * visible, the activity will not be restarted immediately and just keep the activity record in
+ * the stack. It also resets the current override configuration so the activity will use the
+ * configuration according to the latest state.
+ *
+ * @param activityToken The token of the target activity to restart.
+ */
+ void restartActivityProcessIfVisible(in IBinder activityToken);
}
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 8c85ad1..841ff6a 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -149,4 +149,16 @@
* Called when a task snapshot got updated.
*/
void onTaskSnapshotChanged(int taskId, in ActivityManager.TaskSnapshot snapshot);
+
+ /**
+ * Called when the resumed activity is in size compatibility mode and its override configuration
+ * is different from the current one of system.
+ *
+ * @param displayId Id of the display where the activity resides.
+ * @param activityToken Token of the size compatibility mode activity. It will be null when
+ * switching to a activity that is not in size compatibility mode or the
+ * configuration of the activity.
+ * @see com.android.server.wm.AppWindowToken#inSizeCompatMode
+ */
+ void onSizeCompatModeActivityChanged(int displayId, in IBinder activityToken);
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 47ad6d7..97b9176 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -19,6 +19,7 @@
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager.TaskSnapshot;
import android.content.ComponentName;
+import android.os.IBinder;
import android.os.RemoteException;
/**
@@ -155,4 +156,10 @@
@UnsupportedAppUsage
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
}
+
+ @Override
+ @UnsupportedAppUsage
+ public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
+ throws RemoteException {
+ }
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ccfbde5..4c4393d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2002,12 +2002,12 @@
<!-- Must be required by a {@link android.telecom.PhoneAccountSuggestionService},
to ensure that only the system can bind to it.
- <p>Protection level: signature
+ <p>Protection level: signature|privileged
@SystemApi
@hide
-->
<permission android:name="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|privileged" />
<!-- Must be required by a {@link android.telecom.CallRedirectionService},
to ensure that only the system can bind to it.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1fd9fba..73884a0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17623,6 +17623,16 @@
}
@Override
+ public void killProcess(String processName, int uid, String reason) {
+ synchronized (ActivityManagerService.this) {
+ final ProcessRecord proc = getProcessRecordLocked(processName, uid,
+ true /* keepIfLarge */);
+ mProcessList.removeProcessLocked(proc, false /* callerWillRestart */,
+ true /* allowRestart */, reason);
+ }
+ }
+
+ @Override
public boolean hasRunningActivity(int uid, @Nullable String packageName) {
if (packageName == null) return false;
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index f882fde..a33b454d 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -126,6 +126,13 @@
private boolean mSingleTaskInstance;
/**
+ * Non-null if the last size compatibility mode activity is using non-native screen
+ * configuration. The activity is not able to put in multi-window mode, so it exists only one
+ * per display.
+ */
+ private ActivityRecord mLastCompatModeActivity;
+
+ /**
* A focusable stack that is purposely to be positioned at the top. Although the stack may not
* have the topmost index, it is used as a preferred candidate to prevent being unable to resume
* target stack properly when there are other focusable always-on-top stacks.
@@ -1032,6 +1039,28 @@
mSplitScreenPrimaryStack = null;
}
+ /** Checks whether the given activity is in size compatibility mode and notifies the change. */
+ void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
+ if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ // The callback is only interested in the foreground changes of fullscreen activity.
+ return;
+ }
+ if (!r.inSizeCompatMode()) {
+ if (mLastCompatModeActivity != null) {
+ mService.getTaskChangeNotificationController()
+ .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
+ }
+ mLastCompatModeActivity = null;
+ return;
+ }
+ if (mLastCompatModeActivity == r) {
+ return;
+ }
+ mLastCompatModeActivity = r;
+ mService.getTaskChangeNotificationController()
+ .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
+ }
+
ActivityStack getSplitScreenPrimaryStack() {
return mSplitScreenPrimaryStack;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1783591..4706930 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -101,6 +101,7 @@
import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.wm.ActivityStack.ActivityState.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
@@ -160,6 +161,7 @@
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PipModeChangeItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
import android.app.servertransaction.TopResumedActivityChangeItem;
import android.app.servertransaction.WindowVisibilityItem;
import android.app.usage.UsageEvents.Event;
@@ -2128,6 +2130,11 @@
r.icicle = null;
r.haveState = false;
}
+
+ final ActivityDisplay display = r.getDisplay();
+ if (display != null) {
+ display.handleActivitySizeCompatModeIfNeeded(r);
+ }
}
/**
@@ -2189,7 +2196,8 @@
final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState,
CharSequence description) {
final ActivityStack stack = getActivityStack();
- if (mState != STOPPING) {
+ final boolean isStopping = mState == STOPPING;
+ if (!isStopping && mState != RESTARTING_PROCESS) {
Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this);
stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
return;
@@ -2212,7 +2220,9 @@
if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)");
stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this);
stopped = true;
- setState(STOPPED, "activityStoppedLocked");
+ if (isStopping) {
+ setState(STOPPED, "activityStoppedLocked");
+ }
if (mAppWindowToken != null) {
mAppWindowToken.notifyAppStopped();
@@ -2693,13 +2703,39 @@
}
/**
+ * @return {@code true} if this activity is in size compatibility mode that uses the different
+ * density or bounds from its parent.
+ */
+ boolean inSizeCompatMode() {
+ if (!shouldUseSizeCompatMode()) {
+ return false;
+ }
+ final Configuration parentConfig = getParent().getConfiguration();
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
+ // fields should be changed with density and bounds, so here only compares the most
+ // significant field.
+ if (parentConfig.densityDpi != resolvedConfig.densityDpi) {
+ return true;
+ }
+ final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ final Rect parentBounds = parentAppBounds != null
+ ? parentAppBounds : parentConfig.windowConfiguration.getBounds();
+ final Rect overrideBounds = resolvedConfig.windowConfiguration.getBounds();
+ // If the width or height is the same as parent, it is already the best fit of the override
+ // bounds, therefore this condition is considered as not size compatibility mode.
+ return parentBounds.width() != overrideBounds.width()
+ && parentBounds.height() != overrideBounds.height();
+ }
+
+ /**
* Indicates the activity will keep the bounds and screen configuration when it was first
* launched, no matter how its parent changes.
*
* @return {@code true} if this activity is declared as non-resizable and fixed orientation or
* aspect ratio.
*/
- private boolean inSizeCompatMode() {
+ private boolean shouldUseSizeCompatMode() {
return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
// The configuration of non-standard type should be enforced by system.
&& isActivityTypeStandard()
@@ -2708,8 +2744,8 @@
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateOverrideConfiguration() {
- final boolean inSizeCompatMode = inSizeCompatMode();
- if (inSizeCompatMode) {
+ final boolean shouldUseSizeCompatMode = shouldUseSizeCompatMode();
+ if (shouldUseSizeCompatMode) {
if (!matchParentBounds()) {
// The override configuration is set only once in size compatible mode.
return;
@@ -2724,7 +2760,7 @@
computeBounds(mTmpBounds);
- if (inSizeCompatMode && mTmpBounds.isEmpty()) {
+ if (shouldUseSizeCompatMode && mTmpBounds.isEmpty()) {
mTmpBounds.set(task.getWindowConfiguration().getBounds());
}
if (mTmpBounds.equals(getRequestedOverrideBounds())) {
@@ -2736,7 +2772,7 @@
overrideConfig.unset();
if (!mTmpBounds.isEmpty()) {
overrideConfig.windowConfiguration.setBounds(mTmpBounds);
- if (inSizeCompatMode) {
+ if (shouldUseSizeCompatMode) {
// Ensure the screen related fields are set. It is used to prevent activity relaunch
// when moving between displays. For screenWidthDp and screenWidthDp, because they
// are relative to bounds and density, they will be calculated in
@@ -2771,7 +2807,7 @@
}
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- if (!inSizeCompatMode()) {
+ if (!shouldUseSizeCompatMode()) {
computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
ORIENTATION_UNDEFINED, true /* insideParentBounds */);
return;
@@ -2851,6 +2887,11 @@
getResolvedOverrideConfiguration().seq;
mAppWindowToken.onMergedOverrideConfigurationChanged();
}
+
+ final ActivityDisplay display = getDisplay();
+ if (display != null) {
+ display.handleActivitySizeCompatModeIfNeeded(this);
+ }
}
/** Returns true if the configuration is compatible with this activity. */
@@ -2985,7 +3026,7 @@
* state. This is useful for the case where we know the activity will be
* visible soon and we want to ensure its configuration before we make it
* visible.
- * @return True if the activity was relaunched and false if it wasn't relaunched because we
+ * @return False if the activity was relaunched and true if it wasn't relaunched because we
* can't or the app handles the specific configuration that is changing.
*/
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
@@ -3307,6 +3348,54 @@
preserveWindowOnDeferredRelaunch = false;
}
+ /**
+ * Request the process of the activity to restart with its saved state (from
+ * {@link android.app.Activity#onSaveInstanceState}) if possible. It also forces to recompute
+ * the override configuration. Note if the activity is in background, the process will be killed
+ * directly with keeping its record.
+ */
+ void restartProcessIfVisible() {
+ Slog.i(TAG, "Request to restart process of " + this);
+
+ // Reset the existing override configuration to the latest configuration.
+ getRequestedOverrideConfiguration().setToDefaults();
+ getResolvedOverrideConfiguration().setToDefaults();
+ if (visible) {
+ // Configuration will be ensured when becoming visible, so if it is already visible,
+ // then the manual update is needed.
+ updateOverrideConfiguration();
+ }
+
+ if (!attachedToProcess()) {
+ return;
+ }
+
+ // The restarting state avoids removing this record when process is died.
+ setState(RESTARTING_PROCESS, "restartActivityProcess");
+
+ if (!visible || haveState) {
+ // Kill its process immediately because the activity should be in background.
+ // The activity state will be update to {@link #DESTROYED} in
+ // {@link ActivityStack#cleanUpActivityLocked} when handling process died.
+ mAtmService.mH.post(() -> mAtmService.mAmInternal.killProcess(
+ app.mName, app.mUid, "restartActivityProcess"));
+ return;
+ }
+
+ if (mAppWindowToken != null) {
+ mAppWindowToken.startFreezingScreen();
+ }
+ // The process will be killed until the activity reports stopped with saved state (see
+ // {@link ActivityTaskManagerService.activityStopped}).
+ try {
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception thrown during restart " + this, e);
+ }
+ mStackSupervisor.scheduleRestartTimeout(this);
+ }
+
private boolean isProcessRunning() {
WindowProcessController proc = app;
if (proc == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 82c0e21..ad98970 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -297,7 +297,8 @@
STOPPED,
FINISHING,
DESTROYING,
- DESTROYED
+ DESTROYED,
+ RESTARTING_PROCESS
}
@VisibleForTesting
@@ -4741,7 +4742,8 @@
// it has failed more than twice. Skip activities that's already finishing
// cleanly by itself.
remove = false;
- } else if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
+ } else if ((!r.haveState && !r.stateNotNeeded
+ && !r.isState(ActivityState.RESTARTING_PROCESS)) || r.finishing) {
// Don't currently have state for the activity, or
// it is finishing -- always remove it.
remove = true;
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index d1108cc..a8e8c7c5 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -179,6 +179,7 @@
static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3;
static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4;
static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
+ static final int RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16;
@@ -2389,6 +2390,16 @@
mHandler.sendEmptyMessageDelayed(SLEEP_TIMEOUT_MSG, SLEEP_TIMEOUT);
}
+ void removeRestartTimeouts(ActivityRecord r) {
+ mHandler.removeMessages(RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG, r);
+ }
+
+ final void scheduleRestartTimeout(ActivityRecord r) {
+ removeRestartTimeouts(r);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG, r),
+ WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION);
+ }
+
void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
int preferredDisplayId, ActivityStack actualStack) {
handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
@@ -2655,6 +2666,22 @@
}
}
} break;
+ case RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG: {
+ final ActivityRecord r = (ActivityRecord) msg.obj;
+ String processName = null;
+ int uid = 0;
+ synchronized (mService.mGlobalLock) {
+ if (r.attachedToProcess()
+ && r.isState(ActivityStack.ActivityState.RESTARTING_PROCESS)) {
+ processName = r.app.mName;
+ uid = r.app.mUid;
+ }
+ }
+ if (processName != null) {
+ mService.mAmInternal.killProcess(processName, uid,
+ "restartActivityProcessTimeout");
+ }
+ } break;
case REPORT_HOME_CHANGED_MSG: {
synchronized (mService.mGlobalLock) {
mHandler.removeMessages(REPORT_HOME_CHANGED_MSG);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3255bc6..75f299c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1666,13 +1666,32 @@
final long origId = Binder.clearCallingIdentity();
+ String restartingName = null;
+ int restartingUid = 0;
+ final ActivityRecord r;
synchronized (mGlobalLock) {
- final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ r = ActivityRecord.isInStackLocked(token);
if (r != null) {
+ if (r.attachedToProcess()
+ && r.isState(ActivityStack.ActivityState.RESTARTING_PROCESS)) {
+ // The activity was requested to restart from
+ // {@link #restartActivityProcessIfVisible}.
+ restartingName = r.app.mName;
+ restartingUid = r.app.mUid;
+ }
r.activityStoppedLocked(icicle, persistentState, description);
}
}
+ if (restartingName != null) {
+ // In order to let the foreground activity can be restarted with its saved state from
+ // {@link android.app.Activity#onSaveInstanceState}, the kill operation is postponed
+ // until the activity reports stopped with the state. And the activity record will be
+ // kept because the record state is restarting, then the activity will be restarted
+ // immediately if it is still the top one.
+ mStackSupervisor.removeRestartTimeouts(r);
+ mAmInternal.killProcess(restartingName, restartingUid, "restartActivityProcess");
+ }
mAmInternal.trimApplications();
Binder.restoreCallingIdentity(origId);
@@ -2014,6 +2033,23 @@
}
@Override
+ public void restartActivityProcessIfVisible(IBinder activityToken) {
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "restartActivityProcess()");
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(activityToken);
+ if (r == null) {
+ return;
+ }
+ r.restartProcessIfVisible();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ @Override
public boolean removeTask(int taskId) {
enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()");
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 42d2583..3d57219 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -24,6 +24,7 @@
import android.content.ComponentName;
import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
@@ -51,6 +52,7 @@
private static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG = 18;
private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG = 19;
+ private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -143,6 +145,10 @@
l.onTaskSnapshotChanged(m.arg1, (TaskSnapshot) m.obj);
};
+ private final TaskStackConsumer mOnSizeCompatModeActivityChanged = (l, m) -> {
+ l.onSizeCompatModeActivityChanged(m.arg1, (IBinder) m.obj);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -216,6 +222,9 @@
case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
break;
+ case NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG:
+ forAllRemoteListeners(mOnSizeCompatModeActivityChanged, msg);
+ break;
}
}
}
@@ -438,4 +447,15 @@
forAllLocalListeners(mNotifyTaskSnapshotChanged, msg);
msg.sendToTarget();
}
+
+ /**
+ * Notify listeners that whether a size compatibility mode activity is using the override
+ * bounds which is not fit its parent.
+ */
+ void notifySizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
+ final Message msg = mHandler.obtainMessage(NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG,
+ displayId, 0 /* unused */, activityToken);
+ forAllLocalListeners(mOnSizeCompatModeActivityChanged, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index 56f4a85..85e8a14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -40,13 +40,19 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
+import android.app.TaskStackListener;
+import android.content.pm.ActivityInfo;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
-import org.junit.Before;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
/**
* Tests for the {@link ActivityDisplay} class.
*
@@ -331,4 +337,45 @@
verify(mSupervisor).removeTaskByIdLocked(eq(task1.taskId), anyBoolean(), anyBoolean(),
any());
}
+
+ /**
+ * Ensures that {@link TaskStackListener} can receive callback about the activity in size
+ * compatibility mode.
+ */
+ @Test
+ public void testHandleActivitySizeCompatMode() throws Exception {
+ final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final ActivityRecord activity = createFullscreenStackWithSimpleActivityAt(
+ display).topRunningActivityLocked();
+ activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
+ activity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ activity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ activity.getTaskRecord().getConfiguration().windowConfiguration.getBounds().set(
+ 0, 0, 1000, 2000);
+
+ final ArrayList<CompletableFuture<IBinder>> resultWrapper = new ArrayList<>();
+ mService.getTaskChangeNotificationController().registerTaskStackListener(
+ new TaskStackListener() {
+ @Override
+ public void onSizeCompatModeActivityChanged(int displayId,
+ IBinder activityToken) {
+ resultWrapper.get(0).complete(activityToken);
+ }
+ });
+
+ // Expect the exact component name when the activity is in size compatible mode.
+ activity.getResolvedOverrideConfiguration().windowConfiguration.getBounds().set(
+ 0, 0, 800, 1600);
+ resultWrapper.add(new CompletableFuture<>());
+ display.handleActivitySizeCompatModeIfNeeded(activity);
+
+ assertEquals(activity.appToken, resultWrapper.get(0).get(2, TimeUnit.SECONDS));
+
+ // Expect null component name when switching to non-size-compat mode activity.
+ activity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ resultWrapper.set(0, new CompletableFuture<>());
+ display.handleActivitySizeCompatModeIfNeeded(activity);
+
+ assertNull(resultWrapper.get(0).get(2, TimeUnit.SECONDS));
+ }
}
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 1319bad..457d9c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -21,6 +21,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -38,6 +39,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -171,6 +173,24 @@
}
@Test
+ public void testRestartProcessIfVisible() {
+ doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
+ mActivity.getParent().getWindowConfiguration().setAppBounds(0, 0, 500, 1000);
+ mActivity.visible = true;
+ mActivity.haveState = false;
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+ mActivity.info.maxAspectRatio = 1.5f;
+ mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
+ final Rect originalOverrideBounds = new Rect(0, 0, 400, 600);
+ mActivity.setBounds(originalOverrideBounds);
+
+ mService.restartActivityProcessIfVisible(mActivity.appToken);
+
+ assertEquals(ActivityStack.ActivityState.RESTARTING_PROCESS, mActivity.getState());
+ assertNotEquals(originalOverrideBounds, mActivity.getBounds());
+ }
+
+ @Test
public void testsApplyOptionsLocked() {
ActivityOptions activityOptions = ActivityOptions.makeBasic();
diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
index 2589d95..3799cf3 100644
--- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -18,6 +18,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -67,15 +69,10 @@
private boolean mShouldAutoSelect;
/**
- * Creates a new instance of {@link PhoneAccountSuggestion}. This constructor is intended for
- * use by apps implementing a {@link PhoneAccountSuggestionService}, and generally should not be
- * used by dialer apps other than for testing purposes.
- *
- * @param handle The {@link PhoneAccountHandle} for this suggestion.
- * @param reason The reason for this suggestion
- * @param shouldAutoSelect Whether the dialer should automatically place the call using this
- * account. See {@link #shouldAutoSelect()}.
+ * @hide
*/
+ @SystemApi
+ @TestApi
public PhoneAccountSuggestion(@NonNull PhoneAccountHandle handle, @SuggestionReason int reason,
boolean shouldAutoSelect) {
this.mHandle = handle;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 63f47e7..29a18d2 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -816,7 +816,7 @@
* The requested {@link android.net.wifi.p2p.WifiP2pDevice} is available.
* @param wifiP2pDevice Wi-Fi p2p {@link android.net.wifi.p2p.WifiP2pDevice}
*/
- void onDeviceInfoAvailable(WifiP2pDevice wifiP2pDevice);
+ void onDeviceInfoAvailable(@Nullable WifiP2pDevice wifiP2pDevice);
}
/**