summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-rw-r--r--core/java/android/app/Activity.java23
-rw-r--r--core/java/android/app/ActivityThread.java61
-rw-r--r--core/java/android/app/ClientTransactionHandler.java10
-rw-r--r--core/java/android/app/servertransaction/TopResumedActivityChangeItem.java112
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java1
-rw-r--r--services/core/java/com/android/server/wm/RootActivityContainer.java24
9 files changed, 250 insertions, 5 deletions
diff --git a/api/current.txt b/api/current.txt
index 1520a01a3965..9e7dbbff664e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3802,6 +3802,7 @@ package android.app {
method @Deprecated public void onStateNotSaved();
method @CallSuper protected void onStop();
method protected void onTitleChanged(CharSequence, int);
+ method public void onTopResumedActivityChanged(boolean);
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
method public void onTrimMemory(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 1063be4c5c7d..7bdf600dc5f2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1808,6 +1808,29 @@ public class Activity extends ContextThemeWrapper
mCalled = true;
}
+ /**
+ * Called when activity gets or looses the top resumed position in the system.
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} multiple activities can be resumed
+ * at the same time in multi-window and multi-display modes. This callback should be used
+ * instead of {@link #onResume()} as an indication that the activity can try to open
+ * exclusive-access devices like camera.</p>
+ *
+ * <p>It will always be delivered after the activity was resumed and before it is paused. In
+ * some cases it might be skipped and activity can go straight from {@link #onResume()} to
+ * {@link #onPause()} without receiving the top resumed state.</p>
+ *
+ * @param isTopResumedActivity {@code true} if it's the topmost resumed activity in the system,
+ * {@code false} otherwise. A call with this as {@code true} will
+ * always be followed by another one with {@code false}.
+ *
+ * @see #onResume()
+ * @see #onPause()
+ * @see #onWindowFocusChanged(boolean)
+ */
+ public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+ }
+
void setVoiceInteractor(IVoiceInteractor voiceInteractor) {
if (mVoiceInteractor != null) {
for (Request activeRequest: mVoiceInteractor.getActiveRequests()) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a3243a5de72a..2c56a4a613db 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -451,6 +451,14 @@ public final class ActivityThread extends ClientTransactionHandler {
ViewRootImpl.ActivityConfigCallback configCallback;
ActivityClientRecord nextIdle;
+ // Indicates whether this activity is currently the topmost resumed one in the system.
+ // This holds the last reported value from server.
+ boolean isTopResumedActivity;
+ // This holds the value last sent to the activity. This is needed, because an update from
+ // server may come at random time, but we always need to report changes between ON_RESUME
+ // and ON_PAUSE to the app.
+ boolean lastReportedTopResumedState;
+
ProfilerInfo profilerInfo;
@UnsupportedAppUsage
@@ -3302,16 +3310,14 @@ public final class ActivityThread extends ClientTransactionHandler {
final boolean resumed = !r.paused;
if (resumed) {
r.activity.mTemporaryPause = true;
- mInstrumentation.callActivityOnPause(r.activity);
+ performPauseActivityIfNeeded(r, "performNewIntents");
}
checkAndBlockForNetworkAccess();
deliverNewIntents(r, intents);
if (resumed) {
- r.activity.performResume(false, "performNewIntents");
+ performResumeActivity(token, false, "performNewIntents");
r.activity.mTemporaryPause = false;
- }
-
- if (r.paused && andPause) {
+ } else if (andPause) {
// In this case the activity was in the paused state when we delivered the intent,
// to guarantee onResume gets called after onNewIntent we temporarily resume the
// activity and pause again as the caller wanted.
@@ -3964,6 +3970,8 @@ public final class ActivityThread extends ClientTransactionHandler {
r.state = null;
r.persistentState = null;
r.setState(ON_RESUME);
+
+ reportTopResumedActivityChanged(r, r.isTopResumedActivity);
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to resume activity "
@@ -4118,6 +4126,45 @@ public final class ActivityThread extends ClientTransactionHandler {
Looper.myQueue().addIdleHandler(new Idler());
}
+
+ @Override
+ public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
+ ActivityClientRecord r = mActivities.get(token);
+ if (r == null || r.activity == null) {
+ Slog.w(TAG, "Not found target activity to report position change for token: " + token);
+ return;
+ }
+
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
+ }
+
+ if (r.isTopResumedActivity == onTop) {
+ throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+ }
+
+ r.isTopResumedActivity = onTop;
+
+ if (r.getLifecycleState() == ON_RESUME) {
+ reportTopResumedActivityChanged(r, onTop);
+ } else {
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState());
+ }
+ }
+ }
+
+ /**
+ * Call {@link Activity#onTopResumedActivityChanged(boolean)} if its top resumed state changed
+ * since the last report.
+ */
+ private void reportTopResumedActivityChanged(ActivityClientRecord r, boolean onTop) {
+ if (r.lastReportedTopResumedState != onTop) {
+ r.lastReportedTopResumedState = onTop;
+ r.activity.onTopResumedActivityChanged(onTop);
+ }
+ }
+
@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
int configChanges, PendingTransactionActions pendingActions, String reason) {
@@ -4209,6 +4256,10 @@ public final class ActivityThread extends ClientTransactionHandler {
return;
}
+ // Always reporting top resumed position loss when pausing an activity. If necessary, it
+ // will be restored in performResumeActivity().
+ reportTopResumedActivityChanged(r, false /* onTop */);
+
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 07dbb6bee9fd..70badfae4a20 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -105,6 +105,16 @@ public abstract class ClientTransactionHandler {
boolean isForward, String reason);
/**
+ * Notify the activity about top resumed state change.
+ * @param token Target activity token.
+ * @param isTopResumedActivity Current state of the activity, {@code true} if it's the
+ * topmost resumed activity in the system, {@code false} otherwise.
+ * @param reason Reason for performing this operation.
+ */
+ public abstract void handleTopResumedActivityChanged(IBinder token,
+ boolean isTopResumedActivity, String reason);
+
+ /**
* Stop the activity.
* @param token Target activity token.
* @param show Flag indicating whether activity is still shown.
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
new file mode 100644
index 000000000000..4064a02e54a8
--- /dev/null
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 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 android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Top resumed activity changed callback.
+ * @hide
+ */
+public class TopResumedActivityChangeItem extends ClientTransactionItem {
+
+ private boolean mOnTop;
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem");
+ client.handleTopResumedActivityChanged(token, mOnTop, "topResumedActivityChangeItem");
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // ObjectPoolItem implementation
+
+ private TopResumedActivityChangeItem() {}
+
+ /** Obtain an instance initialized with provided params. */
+ public static TopResumedActivityChangeItem obtain(boolean onTop) {
+ TopResumedActivityChangeItem instance =
+ ObjectPool.obtain(TopResumedActivityChangeItem.class);
+ if (instance == null) {
+ instance = new TopResumedActivityChangeItem();
+ }
+ instance.mOnTop = onTop;
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ mOnTop = false;
+ ObjectPool.recycle(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mOnTop);
+ }
+
+ /** Read from Parcel. */
+ private TopResumedActivityChangeItem(Parcel in) {
+ mOnTop = in.readBoolean();
+ }
+
+ public static final Creator<TopResumedActivityChangeItem> CREATOR =
+ new Creator<TopResumedActivityChangeItem>() {
+ public TopResumedActivityChangeItem createFromParcel(Parcel in) {
+ return new TopResumedActivityChangeItem(in);
+ }
+
+ public TopResumedActivityChangeItem[] newArray(int size) {
+ return new TopResumedActivityChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final TopResumedActivityChangeItem other = (TopResumedActivityChangeItem) o;
+ return mOnTop == other.mOnTop;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mOnTop ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "TopResumedActivityChangeItem{onTop=" + mOnTop + "}";
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b7c35c083174..8f39f3d6e463 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -161,6 +161,7 @@ import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PipModeChangeItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.TopResumedActivityChangeItem;
import android.app.servertransaction.WindowVisibilityItem;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
@@ -692,6 +693,26 @@ final class ActivityRecord extends ConfigurationContainer {
}
}
+ void scheduleTopResumedActivityChanged(boolean onTop) {
+ if (!attachedToProcess()) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.w(TAG, "Can't report activity position update - client not running"
+ + ", activityRecord=" + this);
+ }
+ return;
+ }
+ try {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Sending position change to " + this + ", onTop: " + onTop);
+ }
+
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ TopResumedActivityChangeItem.obtain(onTop));
+ } catch (RemoteException e) {
+ // If process died, whatever.
+ }
+ }
+
void updateMultiWindowMode() {
if (task == null || task.getStack() == null || !attachedToProcess()) {
return;
@@ -3099,6 +3120,7 @@ final class ActivityRecord extends ConfigurationContainer {
transaction.addCallback(callbackItem);
transaction.setLifecycleStateRequest(lifecycleItem);
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+ mRootActivityContainer.updateTopResumedActivityIfNeeded();
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// request resume if this activity is currently resumed, which implies we aren't
// sleeping.
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3aef8e1f84bf..5d56d24d8949 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2524,6 +2524,7 @@ class ActivityStack extends ConfigurationContainer {
// Protect against recursion.
mInResumeTopActivity = true;
result = resumeTopActivityInnerLocked(prev, options);
+ mRootActivityContainer.updateTopResumedActivityIfNeeded();
// When resuming the top activity, it may be necessary to pause the top activity (for
// example, returning to the lock screen. We suppress the normal pause logic in
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a83ef34f1cac..c8a150beecfa 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -840,6 +840,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// Schedule transaction.
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
+ mRootActivityContainer.updateTopResumedActivityIfNeeded();
if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
&& mService.mHasHeavyWeightFeature) {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index c4a853dc3483..47b3d3023522 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -175,6 +175,12 @@ class RootActivityContainer extends ConfigurationContainer
private ActivityDisplay mDefaultDisplay;
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+ /**
+ * Cached value of the topmost resumed activity in the system. Updated when new activity is
+ * resumed.
+ */
+ private ActivityRecord mTopResumedActivity;
+
/** The current user */
int mCurrentUser;
/** Stack id of the front stack when user switched, indexed by userId. */
@@ -1145,6 +1151,23 @@ class RootActivityContainer extends ConfigurationContainer
return result;
}
+ void updateTopResumedActivityIfNeeded() {
+ final ActivityRecord prevTopActivity = mTopResumedActivity;
+ final ActivityStack topStack = getTopDisplayFocusedStack();
+ if (topStack == null || topStack.mResumedActivity == prevTopActivity) {
+ return;
+ }
+ // Clear previous top state
+ if (prevTopActivity != null) {
+ prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */);
+ }
+ // Update the current top activity
+ mTopResumedActivity = topStack.mResumedActivity;
+ if (mTopResumedActivity != null) {
+ mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */);
+ }
+ }
+
void applySleepTokens(boolean applyToStacks) {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
// Set the sleeping state of the display.
@@ -1399,6 +1422,7 @@ class RootActivityContainer extends ConfigurationContainer
mActivityDisplays.remove(display);
mActivityDisplays.add(position, display);
}
+ updateTopResumedActivityIfNeeded();
}
@VisibleForTesting