Merge "[Partial Screenshare] introduce a hidden, permission-protected Activity API" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
15229543c6
@@ -6432,6 +6432,20 @@ public class Activity extends ContextThemeWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the activity's result is immediately returned to the caller when {@link #finish()}
|
||||||
|
* is invoked
|
||||||
|
*
|
||||||
|
* <p>Should be invoked alongside {@link #setResult(int, Intent)}, so the provided results are
|
||||||
|
* in place before finishing. Must only be invoked during MediaProjection setup.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
|
||||||
|
public final void setForceSendResultForMediaProjection() {
|
||||||
|
ActivityClient.getInstance().setForceSendResultForMediaProjection(mToken);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this to set the result that your activity will return to its
|
* Call this to set the result that your activity will return to its
|
||||||
* caller.
|
* caller.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package android.app;
|
package android.app;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
|
import android.annotation.RequiresPermission;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
@@ -184,6 +185,15 @@ public class ActivityClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
|
||||||
|
void setForceSendResultForMediaProjection(IBinder token) {
|
||||||
|
try {
|
||||||
|
getActivityClientController().setForceSendResultForMediaProjection(token);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw e.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isTopOfTask(IBinder token) {
|
public boolean isTopOfTask(IBinder token) {
|
||||||
try {
|
try {
|
||||||
return getActivityClientController().isTopOfTask(token);
|
return getActivityClientController().isTopOfTask(token);
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ interface IActivityClientController {
|
|||||||
boolean finishActivityAffinity(in IBinder token);
|
boolean finishActivityAffinity(in IBinder token);
|
||||||
/** Finish all activities that were started for result from the specified activity. */
|
/** Finish all activities that were started for result from the specified activity. */
|
||||||
void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
|
void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
|
||||||
|
/**
|
||||||
|
* Indicates that when the activity finsihes, the result should be immediately sent to the
|
||||||
|
* originating activity. Must only be invoked during MediaProjection setup.
|
||||||
|
*/
|
||||||
|
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)")
|
||||||
|
void setForceSendResultForMediaProjection(in IBinder token);
|
||||||
|
|
||||||
boolean isTopOfTask(in IBinder token);
|
boolean isTopOfTask(in IBinder token);
|
||||||
boolean willActivityBeVisible(in IBinder token);
|
boolean willActivityBeVisible(in IBinder token);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N
|
|||||||
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
|
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
|
||||||
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
|
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.annotation.ColorInt;
|
import android.annotation.ColorInt;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
@@ -462,8 +463,8 @@ class ActivityClientController extends IActivityClientController.Stub {
|
|||||||
// Explicitly dismissing the activity so reset its relaunch flag.
|
// Explicitly dismissing the activity so reset its relaunch flag.
|
||||||
r.mRelaunchReason = RELAUNCH_REASON_NONE;
|
r.mRelaunchReason = RELAUNCH_REASON_NONE;
|
||||||
} else {
|
} else {
|
||||||
r.finishIfPossible(resultCode, resultData, resultGrants,
|
r.finishIfPossible(resultCode, resultData, resultGrants, "app-request",
|
||||||
"app-request", true /* oomAdj */);
|
true /* oomAdj */);
|
||||||
res = r.finishing;
|
res = r.finishing;
|
||||||
if (!res) {
|
if (!res) {
|
||||||
Slog.i(TAG, "Failed to finish by app-request");
|
Slog.i(TAG, "Failed to finish by app-request");
|
||||||
@@ -524,6 +525,23 @@ class ActivityClientController extends IActivityClientController.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setForceSendResultForMediaProjection(IBinder token) {
|
||||||
|
// Require that this is invoked only during MediaProjection setup.
|
||||||
|
mService.mAmInternal.enforceCallingPermission(
|
||||||
|
Manifest.permission.MANAGE_MEDIA_PROJECTION,
|
||||||
|
"setForceSendResultForMediaProjection");
|
||||||
|
|
||||||
|
final ActivityRecord r;
|
||||||
|
synchronized (mGlobalLock) {
|
||||||
|
r = ActivityRecord.isInRootTaskLocked(token);
|
||||||
|
if (r == null || !r.isInHistory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r.setForceSendResultForMediaProjection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTopOfTask(IBinder token) {
|
public boolean isTopOfTask(IBinder token) {
|
||||||
synchronized (mGlobalLock) {
|
synchronized (mGlobalLock) {
|
||||||
|
|||||||
@@ -595,6 +595,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
|||||||
// pre-NYC apps that don't have a sense of being resized.
|
// pre-NYC apps that don't have a sense of being resized.
|
||||||
int mRelaunchReason = RELAUNCH_REASON_NONE;
|
int mRelaunchReason = RELAUNCH_REASON_NONE;
|
||||||
|
|
||||||
|
private boolean mForceSendResultForMediaProjection = false;
|
||||||
|
|
||||||
TaskDescription taskDescription; // the recents information for this activity
|
TaskDescription taskDescription; // the recents information for this activity
|
||||||
|
|
||||||
// The locusId associated with this activity, if set.
|
// The locusId associated with this activity, if set.
|
||||||
@@ -3257,7 +3259,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
|||||||
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
|
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
|
||||||
resultTo.getUriPermissionsLocked());
|
resultTo.getUriPermissionsLocked());
|
||||||
}
|
}
|
||||||
resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
|
if (mForceSendResultForMediaProjection) {
|
||||||
|
resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
|
||||||
|
resultData, resultGrants, true /* forceSendForMediaProjection */);
|
||||||
|
} else {
|
||||||
|
resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
|
||||||
|
}
|
||||||
resultTo = null;
|
resultTo = null;
|
||||||
} else if (DEBUG_RESULTS) {
|
} else if (DEBUG_RESULTS) {
|
||||||
Slog.v(TAG_RESULTS, "No result destination from " + this);
|
Slog.v(TAG_RESULTS, "No result destination from " + this);
|
||||||
@@ -3451,6 +3458,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setForceSendResultForMediaProjection() {
|
||||||
|
mForceSendResultForMediaProjection = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void prepareActivityHideTransitionAnimationIfOvarlay() {
|
private void prepareActivityHideTransitionAnimationIfOvarlay() {
|
||||||
if (mTaskOverlay) {
|
if (mTaskOverlay) {
|
||||||
prepareActivityHideTransitionAnimation();
|
prepareActivityHideTransitionAnimation();
|
||||||
@@ -4541,6 +4552,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
|||||||
|
|
||||||
void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
|
void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
|
||||||
Intent data, NeededUriGrants dataGrants) {
|
Intent data, NeededUriGrants dataGrants) {
|
||||||
|
sendResult(callingUid, resultWho, requestCode, resultCode, data, dataGrants,
|
||||||
|
false /* forceSendForMediaProjection */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
|
||||||
|
Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
|
||||||
if (callingUid > 0) {
|
if (callingUid > 0) {
|
||||||
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
|
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
|
||||||
getUriPermissionsLocked());
|
getUriPermissionsLocked());
|
||||||
@@ -4549,8 +4566,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
|||||||
if (DEBUG_RESULTS) {
|
if (DEBUG_RESULTS) {
|
||||||
Slog.v(TAG, "Send activity result to " + this
|
Slog.v(TAG, "Send activity result to " + this
|
||||||
+ " : who=" + resultWho + " req=" + requestCode
|
+ " : who=" + resultWho + " req=" + requestCode
|
||||||
+ " res=" + resultCode + " data=" + data);
|
+ " res=" + resultCode + " data=" + data
|
||||||
|
+ " forceSendForMediaProjection=" + forceSendForMediaProjection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isState(RESUMED) && attachedToProcess()) {
|
if (isState(RESUMED) && attachedToProcess()) {
|
||||||
try {
|
try {
|
||||||
final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
|
final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
|
||||||
@@ -4563,9 +4582,63 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedule sending results now for Media Projection setup.
|
||||||
|
if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED,
|
||||||
|
STOPPING, STOPPED)) {
|
||||||
|
final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
|
||||||
|
// Build result to be returned immediately.
|
||||||
|
transaction.addCallback(ActivityResultItem.obtain(
|
||||||
|
List.of(new ResultInfo(resultWho, requestCode, resultCode, data))));
|
||||||
|
// When the activity result is delivered, the activity will transition to RESUMED.
|
||||||
|
// Since the activity is only resumed so the result can be immediately delivered,
|
||||||
|
// return it to its original lifecycle state.
|
||||||
|
ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult();
|
||||||
|
if (lifecycleItem != null) {
|
||||||
|
transaction.setLifecycleStateRequest(lifecycleItem);
|
||||||
|
} else {
|
||||||
|
Slog.w(TAG, "Unable to get the lifecycle item for state " + mState
|
||||||
|
+ " so couldn't immediately send result");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Slog.w(TAG, "Exception thrown sending result to " + this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addResultLocked(null /* from */, resultWho, requestCode, resultCode, data);
|
addResultLocked(null /* from */, resultWho, requestCode, resultCode, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a lifecycle item for the current stat. Only to be used when force sending an
|
||||||
|
* activity result (as part of MeidaProjection setup). Does not support the following states:
|
||||||
|
* {@link State#INITIALIZING}, {@link State#RESTARTING_PROCESS},
|
||||||
|
* {@link State#FINISHING}, {@link State#DESTROYING}, {@link State#DESTROYED}. It does not make
|
||||||
|
* sense to force send a result to an activity in these states. Does not support
|
||||||
|
* {@link State#RESUMED} since a resumed activity will end in the resumed state after handling
|
||||||
|
* the result.
|
||||||
|
*
|
||||||
|
* @return an {@link ActivityLifecycleItem} for the current state, or {@code null} if the
|
||||||
|
* state is not valid.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ActivityLifecycleItem getLifecycleItemForCurrentStateForResult() {
|
||||||
|
switch (mState) {
|
||||||
|
case STARTED:
|
||||||
|
return StartActivityItem.obtain(null);
|
||||||
|
case PAUSING:
|
||||||
|
case PAUSED:
|
||||||
|
return PauseActivityItem.obtain();
|
||||||
|
case STOPPING:
|
||||||
|
case STOPPED:
|
||||||
|
return StopActivityItem.obtain(configChangeFlags);
|
||||||
|
default:
|
||||||
|
// Do not send a result immediately if the activity is in state INITIALIZING,
|
||||||
|
// RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addNewIntentLocked(ReferrerIntent intent) {
|
private void addNewIntentLocked(ReferrerIntent intent) {
|
||||||
if (newIntents == null) {
|
if (newIntents == null) {
|
||||||
newIntents = new ArrayList<>();
|
newIntents = new ArrayList<>();
|
||||||
|
|||||||
@@ -1160,6 +1160,45 @@ public class ActivityRecordTests extends WindowTestsBase {
|
|||||||
assertTrue(lastTransition.allReady());
|
assertTrue(lastTransition.allReady());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFinishActivityIfPossible_sendResultImmediately() {
|
||||||
|
// Create activity representing the source of the activity result.
|
||||||
|
final ComponentName sourceComponent = ComponentName.createRelative(
|
||||||
|
DEFAULT_COMPONENT_PACKAGE_NAME, ".SourceActivity");
|
||||||
|
final ComponentName targetComponent = ComponentName.createRelative(
|
||||||
|
sourceComponent.getPackageName(), ".TargetActivity");
|
||||||
|
|
||||||
|
final ActivityRecord sourceActivity = new ActivityBuilder(mWm.mAtmService)
|
||||||
|
.setComponent(sourceComponent)
|
||||||
|
.setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
|
||||||
|
.setCreateTask(true)
|
||||||
|
.build();
|
||||||
|
sourceActivity.finishing = false;
|
||||||
|
sourceActivity.setState(STOPPED, "test");
|
||||||
|
|
||||||
|
final ActivityRecord targetActivity = new ActivityBuilder(mWm.mAtmService)
|
||||||
|
.setComponent(targetComponent)
|
||||||
|
.setTargetActivity(sourceComponent.getClassName())
|
||||||
|
.setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
|
||||||
|
.setCreateTask(true)
|
||||||
|
.setOnTop(true)
|
||||||
|
.build();
|
||||||
|
targetActivity.finishing = false;
|
||||||
|
targetActivity.setState(RESUMED, "test");
|
||||||
|
targetActivity.resultTo = sourceActivity;
|
||||||
|
targetActivity.setForceSendResultForMediaProjection();
|
||||||
|
|
||||||
|
clearInvocations(mAtm.getLifecycleManager());
|
||||||
|
|
||||||
|
targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
|
||||||
|
|
||||||
|
try {
|
||||||
|
verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
|
||||||
|
any(ClientTransaction.class));
|
||||||
|
} catch (RemoteException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that complete finish request for non-finishing activity is invalid.
|
* Verify that complete finish request for non-finishing activity is invalid.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user