diff options
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 |