diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl index d03ff93be5cee..7b690eaad96c1 100644 --- a/core/java/android/service/quicksettings/IQSService.aidl +++ b/core/java/android/service/quicksettings/IQSService.aidl @@ -15,6 +15,7 @@ */ package android.service.quicksettings; +import android.app.PendingIntent; import android.content.ComponentName; import android.graphics.drawable.Icon; import android.service.quicksettings.Tile; @@ -29,10 +30,10 @@ interface IQSService { String contentDescription); void onShowDialog(in IBinder tile); void onStartActivity(in IBinder tile); + void startActivity(in IBinder tile, in PendingIntent pendingIntent); boolean isLocked(); boolean isSecure(); void startUnlockAndRun(in IBinder tile); - void onDialogHidden(in IBinder tile); void onStartSuccessful(in IBinder tile); } diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java index 40c0ac00a5a9a..d60b225e695ab 100644 --- a/core/java/android/service/quicksettings/Tile.java +++ b/core/java/android/service/quicksettings/Tile.java @@ -16,6 +16,7 @@ package android.service.quicksettings; import android.annotation.Nullable; +import android.app.PendingIntent; import android.graphics.drawable.Icon; import android.os.IBinder; import android.os.Parcel; @@ -66,6 +67,7 @@ public final class Tile implements Parcelable { private CharSequence mSubtitle; private CharSequence mContentDescription; private CharSequence mStateDescription; + private PendingIntent mPendingIntent; // Default to inactive until clients of the new API can update. private int mState = STATE_INACTIVE; @@ -223,6 +225,34 @@ public final class Tile implements Parcelable { } } + /** + * Gets the Activity {@link PendingIntent} to be launched when the tile is clicked. + * @hide + */ + @Nullable + public PendingIntent getActivityLaunchForClick() { + return mPendingIntent; + } + + /** + * Sets an Activity {@link PendingIntent} to be launched when the tile is clicked. + * + * The last value set here will be launched when the user clicks in the tile, instead of + * forwarding the `onClick` message to the {@link TileService}. Set to {@code null} to handle + * the `onClick` in the `TileService` + * (This is the default behavior if this method is never called.) + * @param pendingIntent a PendingIntent for an activity to be launched onclick, or {@code null} + * to handle the clicks in the `TileService`. + * @hide + */ + public void setActivityLaunchForClick(@Nullable PendingIntent pendingIntent) { + if (pendingIntent != null && !pendingIntent.isActivity()) { + throw new IllegalArgumentException(); + } else { + mPendingIntent = pendingIntent; + } + } + @Override public void writeToParcel(Parcel dest, int flags) { if (mIcon != null) { @@ -231,6 +261,12 @@ public final class Tile implements Parcelable { } else { dest.writeByte((byte) 0); } + if (mPendingIntent != null) { + dest.writeByte((byte) 1); + mPendingIntent.writeToParcel(dest, flags); + } else { + dest.writeByte((byte) 0); + } dest.writeInt(mState); TextUtils.writeToParcel(mLabel, dest, flags); TextUtils.writeToParcel(mSubtitle, dest, flags); @@ -244,6 +280,11 @@ public final class Tile implements Parcelable { } else { mIcon = null; } + if (source.readByte() != 0) { + mPendingIntent = PendingIntent.CREATOR.createFromParcel(source); + } else { + mPendingIntent = null; + } mState = source.readInt(); mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index 85502197ea7ed..506b3b81eb9ad 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -20,6 +20,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.Dialog; +import android.app.PendingIntent; import android.app.Service; import android.app.StatusBarManager; import android.content.ComponentName; @@ -335,6 +336,20 @@ public class TileService extends Service { } } + /** + * Starts an {@link android.app.Activity}. + * Will collapse Quick Settings after launching. + * + * @param pendingIntent A PendingIntent for an Activity to be launched immediately. + * @hide + */ + public void startActivityAndCollapse(PendingIntent pendingIntent) { + try { + mService.startActivity(mTileToken, pendingIntent); + } catch (RemoteException e) { + } + } + /** * Gets the {@link Tile} for this service. *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 717ae91f74c52..8ddc3c04d991b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -204,10 +204,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mIsDropEntering; private boolean mIsExiting; - /** The target stage to dismiss to when unlock after folded. */ - @StageType - private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; @@ -976,20 +972,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { - if (ENABLE_SHELL_TRANSITIONS) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); - mSplitTransitions.startDismissTransition(wct, this, - mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); - } else { - exitSplitScreen( - mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, - EXIT_REASON_DEVICE_FOLDED); - } - return; - } - setDividerVisibility(!mKeyguardShowing, null); } @@ -1828,14 +1810,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, sendOnBoundsChanged(); } - private void onFoldedStateChanged(boolean folded) { - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + @VisibleForTesting + void onFoldedStateChanged(boolean folded) { + int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; if (!folded) return; + if (!mMainStage.isActive()) return; + if (mMainStage.isFocused()) { - mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; + topStageAfterFoldDismiss = STAGE_TYPE_MAIN; } else if (mSideStage.isFocused()) { - mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; + topStageAfterFoldDismiss = STAGE_TYPE_SIDE; + } + + if (ENABLE_SHELL_TRANSITIONS) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(topStageAfterFoldDismiss, wct); + mSplitTransitions.startDismissTransition(wct, this, + topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); + } else { + exitSplitScreen( + topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, + EXIT_REASON_DEVICE_FOLDED); } } @@ -2118,7 +2114,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Update divider state after animation so that it is still around and positioned // properly for the animation itself. mSplitLayout.release(); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 3569860b61282..65e1ea881b264 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -155,7 +155,7 @@ public class StageCoordinatorTests extends ShellTestCase { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); // Verify move to undefined stage while split screen not activated moves task to side stage. - when(mMainStage.isActive()).thenReturn(false); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false); mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT, new WindowContainerTransaction()); @@ -163,7 +163,7 @@ public class StageCoordinatorTests extends ShellTestCase { assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); // Verify move to undefined stage after split screen activated moves task based on position. - when(mMainStage.isActive()).thenReturn(true); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, new WindowContainerTransaction()); @@ -262,7 +262,7 @@ public class StageCoordinatorTests extends ShellTestCase { @Test public void testResolveStartStage_afterSplitActivated_retrievesStagePosition() { - when(mMainStage.isActive()).thenReturn(true); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, @@ -320,4 +320,16 @@ public class StageCoordinatorTests extends ShellTestCase { assertTrue(options.getBoolean( KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION)); } + + @Test + public void testExitSplitScreenAfterFolded() { + when(mMainStage.isActive()).thenReturn(true); + when(mMainStage.isFocused()).thenReturn(true); + when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID); + + mStageCoordinator.onFoldedStateChanged(true); + + verify(mStageCoordinator).onSplitScreenExit(); + verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index c4386ab9a3df1..cfda9fd6cb964 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.external; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -51,6 +52,7 @@ import androidx.annotation.WorkerThread; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -92,6 +94,8 @@ public class CustomTile extends QSTileImpl
* It will return {@code true} before initialization, so tiles are not destroyed prematurely.
*/
@Override
@@ -262,6 +266,7 @@ public class CustomTile extends QSTileImpl