reply.writeInt(isForeground ? 1 : 0);
return true;
}
+ case NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
return isForeground;
};
+ @Override
+ public void notifyPinnedStackAnimationEnded() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION, data, reply, 0);
+ data.recycle();
+ reply.recycle();
+ };
+
private IBinder mRemote;
}
public boolean supportsLocalVoiceInteraction() throws RemoteException;
+ public void notifyPinnedStackAnimationEnded() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
int START_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 363;
int STOP_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 364;
int SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 365;
+ int NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 366;
}
* brought to the front or a new Intent is delivered to it.
*/
void onPinnedActivityRestartAttempt();
+
+ /**
+ * Called whenever the pinned stack is done animating a resize.
+ */
+ void onPinnedStackAnimationEnded();
}
public void onPinnedActivityRestartAttempt() {
}
+ @Override
+ public void onPinnedStackAnimationEnded() {
+ }
+
/** Preloads the next task */
public void run() {
RecentsConfiguration config = Recents.getConfiguration();
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP: {
SystemServicesProxy ssp = Recents.getSystemServices();
- PipManager.getInstance().showPipMenu();
+ PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_MENU);
ssp.focusPinnedStack();
return true;
}
case KeyEvent.KEYCODE_DPAD_DOWN: {
SystemServicesProxy ssp = Recents.getSystemServices();
- PipManager.getInstance().showPipOverlay(false);
+ PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
ssp.focusHomeStack();
return true;
}
}
@Override
+ public void onPinnedStackAnimationEnded() {
+ }
+
+ @Override
public void onTaskStackChanged() {
mHandler.removeCallbacks(this);
mHandler.post(this);
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
private static final int MAX_RUNNING_TASKS_COUNT = 10;
- private static final int STATE_NO_PIP = 0;
- private static final int STATE_PIP_OVERLAY = 1;
- private static final int STATE_PIP_MENU = 2;
+ public static final int STATE_NO_PIP = 0;
+ public static final int STATE_PIP_OVERLAY = 1;
+ public static final int STATE_PIP_MENU = 2;
private static final int TASK_ID_NO_PIP = -1;
private static final int INVALID_RESOURCE_TYPE = -1;
+ public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
+ public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
+ private int mSuspendPipResizingReason;
+
private Context mContext;
private IActivityManager mActivityManager;
private int mState = STATE_NO_PIP;
}
if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
- showPipOverlay(false);
+ // Set state to overlay so we show it when the pinned stack animation ends.
+ mState = STATE_PIP_OVERLAY;
launchPipOnboardingActivityIfNeeded();
}
};
movePipToFullscreen();
}
};
+ private final Runnable mOnPinnedStackAnimationEnded = new Runnable() {
+ @Override
+ public void run() {
+ if (mState == STATE_PIP_OVERLAY) {
+ showPipOverlay();
+ } else if (mState == STATE_PIP_MENU) {
+ showPipMenu();
+ }
+ }
+ };
+
+ private final Runnable mResizePinnedStackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ resizePinnedStack(mState);
+ }
+ };
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
if (!hasPipTasks()) {
startPip();
} else if (mState == STATE_PIP_OVERLAY) {
- showPipMenu();
+ resizePinnedStack(STATE_PIP_MENU);
}
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onMoveToFullscreen();
}
- try {
- mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true);
- } catch (RemoteException e) {
- Log.e(TAG, "moveTasksToFullscreenStack failed", e);
- }
+ resizePinnedStack(mState);
}
/**
* stack to the default PIP bound {@link com.android.internal.R.string
* .config_defaultPictureInPictureBounds}.
*/
- public void showPipOverlay(boolean resizeStack) {
+ private void showPipOverlay() {
if (DEBUG) Log.d(TAG, "showPipOverlay()");
mState = STATE_PIP_OVERLAY;
Intent intent = new Intent(mContext, PipOverlayActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchStackId(PINNED_STACK_ID);
- if (resizeStack) {
- options.setLaunchBounds(mPipBound);
- }
mContext.startActivity(intent, options.toBundle());
}
/**
+ * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
+ * @param reason The reason for suspending resizing operations on the Pip.
+ */
+ public void suspendPipResizing(int reason) {
+ if (DEBUG) Log.d(TAG,
+ "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+ mSuspendPipResizingReason |= reason;
+ }
+
+ /**
+ * Resumes resizing operation on the Pip that was previously suspended.
+ * @param reason The reason resizing operations on the Pip was suspended.
+ */
+ public void resumePipResizing(int reason) {
+ if ((mSuspendPipResizingReason & reason) == 0) {
+ return;
+ }
+ if (DEBUG) Log.d(TAG,
+ "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+ mSuspendPipResizingReason &= ~reason;
+ mHandler.post(mResizePinnedStackRunnable);
+ }
+
+ /**
+ * Resize the Pip to the appropriate size for the input state.
+ * @param state In Pip state also used to determine the new size for the Pip.
+ */
+ public void resizePinnedStack(int state) {
+ if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
+ mState = state;
+ Rect bounds;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onPipResizeAboutToStart();
+ }
+ switch (mState) {
+ case STATE_PIP_MENU:
+ bounds = mMenuModePipBound;
+ break;
+ case STATE_NO_PIP:
+ bounds = null;
+ break;
+ default:
+ bounds = mPipBound;
+ break;
+ }
+
+ if (mSuspendPipResizingReason != 0) {
+ if (DEBUG) Log.d(TAG,
+ "resizePinnedStack() deferring mSuspendPipResizingReason=" +
+ mSuspendPipResizingReason);
+ return;
+ }
+
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, bounds, true, true, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "showPipMenu failed", e);
+ }
+ }
+
+ /**
* Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
* stack to the centered PIP bound {@link com.android.internal.R.string
* .config_centeredPictureInPictureBounds}.
*/
- public void showPipMenu() {
+ private void showPipMenu() {
if (DEBUG) Log.d(TAG, "showPipMenu()");
mState = STATE_PIP_MENU;
for (int i = mListeners.size() - 1; i >= 0; --i) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchStackId(PINNED_STACK_ID);
- options.setLaunchBounds(mMenuModePipBound);
mContext.startActivity(intent, options.toBundle());
}
- /**
- * Adds {@link Listener}.
- */
public void addListener(Listener listener) {
mListeners.add(listener);
}
- /**
- * Removes {@link Listener}.
- */
public void removeListener(Listener listener) {
mListeners.remove(listener);
}
@Override
public void onActivityPinned() throws RemoteException {
// Post the message back to the UI thread.
+ if (DEBUG) Log.d(TAG, "onActivityPinned()");
mHandler.post(mOnActivityPinnedRunnable);
}
@Override
public void onPinnedActivityRestartAttempt() {
// Post the message back to the UI thread.
+ if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
mHandler.post(mOnPinnedActivityRestartAttempt);
}
+
+ @Override
+ public void onPinnedStackAnimationEnded() {
+ if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
+ mHandler.post(mOnPinnedStackAnimationEnded);
+ }
}
/**
* A listener interface to receive notification on changes in PIP.
*/
public interface Listener {
- /**
- * Invoked when a PIPed activity is closed.
- */
+ /** Invoked when a PIPed activity is closed. */
void onPipActivityClosed();
- /**
- * Invoked when the PIP menu gets shown.
- */
+ /** Invoked when the PIP menu gets shown. */
void onShowPipMenu();
- /**
- * Invoked when the PIPed activity is returned back to the fullscreen.
- */
+ /** Invoked when the PIPed activity is returned back to the fullscreen. */
void onMoveToFullscreen();
+ /** Invoked when we are above to start resizing the Pip. */
+ void onPipResizeAboutToStart();
}
/**
findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mPipManager.showPipOverlay(true);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
finish();
}
});
@Override
protected void onDestroy() {
- mPipManager.removeListener(this);
super.onDestroy();
+ mPipManager.removeListener(this);
+ mPipManager.resumePipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
@Override
public void onBackPressed() {
- mPipManager.showPipOverlay(true);
+ mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
finish();
}
public void onMoveToFullscreen() {
finish();
}
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ finish();
+ mPipManager.suspendPipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
+ }
}
public void onMoveToFullscreen() {
finish();
}
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ }
}
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
-import android.view.View;
+import android.view.View;
import com.android.systemui.R;
/**
private static final String TAG = "PipOverlayActivity";
private static final boolean DEBUG = false;
- private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 2000;
+ private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 4000;
private final PipManager mPipManager = PipManager.getInstance();
private final Handler mHandler = new Handler();
+ private View mGuideOverlayView;
+ private final Runnable mHideGuideOverlayRunnable = new Runnable() {
+ public void run() {
+ mGuideOverlayView.setVisibility(View.INVISIBLE);
+ }
+ };
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.tv_pip_overlay);
+ mGuideOverlayView = findViewById(R.id.guide_overlay);
mPipManager.addListener(this);
- final View overlayView = findViewById(R.id.guide_overlay);
- // TODO: apply animation
- overlayView.setVisibility(View.VISIBLE);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- overlayView.setVisibility(View.INVISIBLE);
- }
- }, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mHandler.removeCallbacks(mHideGuideOverlayRunnable);
+ mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mHandler.removeCallbacks(mHideGuideOverlayRunnable);
+ finish();
}
@Override
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
mPipManager.removeListener(this);
+ mPipManager.resumePipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH);
}
@Override
public void onMoveToFullscreen() {
finish();
}
+
+ @Override
+ public void onPipResizeAboutToStart() {
+ finish();
+ mPipManager.suspendPipResizing(
+ PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH);
+ }
}
static final int VR_MODE_CHANGE_MSG = 63;
static final int NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG = 64;
static final int NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG = 65;
+ static final int NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG = 66;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
}
break;
}
+ case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG: {
+ synchronized (ActivityManagerService.this) {
+ for (int i = mTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) {
+ try {
+ // Make a one-way callback to the listener
+ mTaskStackListeners.getBroadcastItem(i).onPinnedStackAnimationEnded();
+ } catch (RemoteException e){
+ // Handled by the RemoteCallbackList
+ }
+ }
+ mTaskStackListeners.finishBroadcast();
+ }
+ break;
+ }
case NOTIFY_CLEARTEXT_NETWORK_MSG: {
final int uid = msg.arg1;
final byte[] firstPacket = (byte[]) msg.obj;
final Rect bounds = (mStackSupervisor.getStack(PINNED_STACK_ID) == null)
? mDefaultPinnedStackBounds : null;
- mStackSupervisor.moveActivityToStackLocked(
- r, PINNED_STACK_ID, "enterPictureInPicture", bounds);
+ mStackSupervisor.moveActivityToPinnedStackLocked(
+ r, "enterPictureInPicture", bounds);
}
} finally {
Binder.restoreCallingIdentity(origId);
mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG).sendToTarget();
}
+ /** Notifies all listeners when the pinned stack animation ends. */
+ @Override
+ public void notifyPinnedStackAnimationEnded() {
+ synchronized (this) {
+ mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
+ mHandler.obtainMessage(
+ NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG).sendToTarget();
+ }
+ }
+
@Override
public void notifyCleartextNetwork(int uid, byte[] firstPacket) {
mHandler.obtainMessage(NOTIFY_CLEARTEXT_NETWORK_MSG, uid, 0, firstPacket).sendToTarget();
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ANIMATE;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
return false;
}
- moveActivityToStackLocked(r, PINNED_STACK_ID, "moveTopActivityToPinnedStack", null);
- mWindowManager.animateResizePinnedStack(bounds);
+ moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds);
return true;
}
- void moveActivityToStackLocked(ActivityRecord r, int stackId, String reason, Rect bounds) {
- final TaskRecord task = r.task;
- if (task.mActivities.size() == 1) {
- // There is only one activity in the task. So, we can just move the task over to the
- // stack without re-parenting the activity in a different task.
- moveTaskToStackLocked(
- task.taskId, stackId, ON_TOP, FORCE_FOCUS, reason, true /* animate */);
- } else {
- final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
- stack.moveActivityToStack(r);
- }
-
- if (bounds != null) {
- resizeStackLocked(stackId, bounds, null /* tempTaskBounds */,
- null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true);
+ void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds) {
+ mWindowManager.deferSurfaceLayout();
+ try {
+ final TaskRecord task = r.task;
+
+ // Need to make sure the pinned stack exist so we can resize it below...
+ final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+
+ // Resize the pinned stack to match the current size of the task the activity we are
+ // going to be moving is currently contained in. We do this to have the right starting
+ // animation bounds for the pinned stack to the desired bounds the caller wants.
+ resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+ true /* allowResizeInDockedMode */);
+
+ if (task.mActivities.size() == 1) {
+ // There is only one activity in the task. So, we can just move the task over to
+ // the stack without re-parenting the activity in a different task.
+ moveTaskToStackLocked(
+ task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
+ } else {
+ stack.moveActivityToStack(r);
+ }
+ } finally {
+ mWindowManager.continueSurfaceLayout();
}
- // The task might have already been running and its visibility needs to be synchronized with
- // the visibility of the stack / windows.
+ // The task might have already been running and its visibility needs to be synchronized
+ // with the visibility of the stack / windows.
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- if (stackId == PINNED_STACK_ID) {
- mService.notifyActivityPinnedLocked();
- }
+ mWindowManager.animateResizePinnedStack(bounds);
+ mService.notifyActivityPinnedLocked();
}
void positionTaskInStackLocked(int taskId, int stackId, int position) {
// If the activity is not focusable, we can't resume it, but still would like to
// make sure it becomes visible as it starts (this will also trigger entry
// animation). An example of this are PIP activities.
- mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+ mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
} else {
mTargetStack.addRecentActivityLocked(mStartActivity);
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
mDragResizing = false;
mService.requestTraversal();
}
+ if (mStackId == PINNED_STACK_ID) {
+ try {
+ mService.mActivityManager.notifyPinnedStackAnimationEnded();
+ } catch (RemoteException e) {
+ // I don't believe you...
+ }
+ }
}
@Override