Merge changes from topic 'recents_transition' into nyc-dev
* changes:
Implement transition for docking task in recents #6
Implement transition for docking task in recents #5
Implement transition for docking task in recents #4
Implement transition for docking task in recents #3
Implement transition for docking task in recents #2
Implement transition for docking task in recents #1
Show a scrim activity if task is not resizable
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 32edd4d..77df151 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -29,8 +29,9 @@
* This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
* values between those keyframes for a given animation. The class internal to the animation
* package because it is an implementation detail of how Keyframes are stored and used.
+ * @hide
*/
-class KeyframeSet implements Keyframes {
+public class KeyframeSet implements Keyframes {
int mNumKeyframes;
diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java
index c149bed..e40a86c 100644
--- a/core/java/android/animation/Keyframes.java
+++ b/core/java/android/animation/Keyframes.java
@@ -20,8 +20,9 @@
/**
* This interface abstracts a collection of Keyframe objects and is called by
* ValueAnimator to calculate values between those keyframes for a given animation.
+ * @hide
*/
-interface Keyframes extends Cloneable {
+public interface Keyframes extends Cloneable {
/**
* Sets the TypeEvaluator to be used when calculating animated values. This object
diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java
index 8230ac5..50a490e 100644
--- a/core/java/android/animation/PathKeyframes.java
+++ b/core/java/android/animation/PathKeyframes.java
@@ -34,8 +34,9 @@
* Typically, the returned type is a PointF, but the individual components can be extracted
* as either an IntKeyframes or FloatKeyframes.
* </p>
+ * @hide
*/
-class PathKeyframes implements Keyframes {
+public class PathKeyframes implements Keyframes {
private static final int FRACTION_OFFSET = 0;
private static final int X_OFFSET = 1;
private static final int Y_OFFSET = 2;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7310d67..5116634 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -114,4 +114,15 @@
* values.
*/
public abstract void notifyAppTransitionStarting(int reason);
+
+ /**
+ * Callback for window manager to let activity manager know that the app transition was
+ * cancelled.
+ */
+ public abstract void notifyAppTransitionCancelled();
+
+ /**
+ * Callback for window manager to let activity manager know that the app transition is finished.
+ */
+ public abstract void notifyAppTransitionFinished();
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 167e6cb..2846798 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -154,6 +154,12 @@
private static final String KEY_LAUNCH_STACK_ID = "android.activity.launchStackId";
/**
+ * The task id the activity should be launched into.
+ * @hide
+ */
+ private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
+
+ /**
* Where the docked stack should be positioned.
* @hide
*/
@@ -224,6 +230,7 @@
private int mExitCoordinatorIndex;
private PendingIntent mUsageTimeReport;
private int mLaunchStackId = INVALID_STACK_ID;
+ private int mLaunchTaskId = -1;
private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
private AppTransitionAnimationSpec mAnimSpecs[];
@@ -766,6 +773,7 @@
break;
}
mLaunchStackId = opts.getInt(KEY_LAUNCH_STACK_ID, INVALID_STACK_ID);
+ mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
if (opts.containsKey(KEY_ANIM_SPECS)) {
Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
@@ -927,6 +935,21 @@
mLaunchStackId = launchStackId;
}
+ /**
+ * Sets the task the activity will be launched in.
+ * @hide
+ */
+ public void setLaunchTaskId(int taskId) {
+ mLaunchTaskId = taskId;
+ }
+
+ /**
+ * @hide
+ */
+ public int getLaunchTaskId() {
+ return mLaunchTaskId;
+ }
+
/** @hide */
public int getDockCreateMode() {
return mDockCreateMode;
@@ -1079,6 +1102,7 @@
break;
}
b.putInt(KEY_LAUNCH_STACK_ID, mLaunchStackId);
+ b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
if (mAnimSpecs != null) {
b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 63a6829..d28f1fb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -803,7 +803,12 @@
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
- if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+
+ // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
+ // generally not allowed, except if the caller specifies the task id the activity should
+ // be launched in.
+ if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+ && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 6432558..fa67529 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -35,4 +35,9 @@
* Called whenever the pinned stack is done animating a resize.
*/
void onPinnedStackAnimationEnded();
+
+ /**
+ * Called when we launched an activity that we forced to be resizable.
+ */
+ void onActivityForcedResizable(String packageName, int taskId);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index b243cac..9e5c238 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -88,6 +88,11 @@
*/
void appTransitionStarting(long statusBarAnimationsStartTime, long statusBarAnimationsDuration);
+ /**
+ * Notifies the status bar that an app transition is done.
+ */
+ void appTransitionFinished();
+
void showAssistDisclosure();
void startAssist(in Bundle args);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 589eac6..9839446 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -263,6 +263,14 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".stackdivider.ForcedResizableInfoActivity"
+ android:theme="@style/ForcedResizableTheme"
+ android:excludeFromRecents="true"
+ android:stateNotNeeded="true"
+ android:exported="false">
+ </activity>
+
<!-- Callback for dismissing screenshot notification after a share target is picked -->
<receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
android:process=":screenshot"
diff --git a/packages/SystemUI/res/anim/forced_resizable_enter.xml b/packages/SystemUI/res/anim/forced_resizable_enter.xml
new file mode 100644
index 0000000..01b8fdb
--- /dev/null
+++ b/packages/SystemUI/res/anim/forced_resizable_enter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:interpolator="@android:interpolator/linear_out_slow_in"
+ android:duration="280" />
diff --git a/packages/SystemUI/res/anim/forced_resizable_exit.xml b/packages/SystemUI/res/anim/forced_resizable_exit.xml
new file mode 100644
index 0000000..6f316a7
--- /dev/null
+++ b/packages/SystemUI/res/anim/forced_resizable_exit.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:duration="160"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:zAdjustment="top"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/forced_resizable_activity.xml b/packages/SystemUI/res/layout/forced_resizable_activity.xml
new file mode 100644
index 0000000..df245bc
--- /dev/null
+++ b/packages/SystemUI/res/layout/forced_resizable_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/forced_resizable_info_text"
+ android:textColor="#ffffff"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4f3db05..b9aa26b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -151,6 +151,7 @@
<color name="docked_divider_background">#ff000000</color>
<color name="docked_divider_handle">#ffffff</color>
+ <drawable name="forced_resizable_background">#80000000</drawable>
<color name="default_remote_input_background">@*android:color/notification_default_color</color>
<color name="remote_input_hint">#99ffffff</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6f0f30d..25ecd888 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1530,4 +1530,6 @@
<!-- Accessibility action for moving down the docked stack divider [CHAR LIMIT=NONE] -->
<string name="accessibility_action_divider_move_right">Move right</string>
+ <!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity and that things might crash/not work properly [CHAR LIMIT=NONE] -->
+ <string name="forced_resizable_info_text">App may not work with multi-window</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 21ad216..2b134af 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -40,6 +40,18 @@
<item name="android:windowBackground">@android:color/black</item>
</style>
+ <!-- Theme used for the activity that shows when the system forced an app to be resizable -->
+ <style name="ForcedResizableTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
+ <item name="android:windowBackground">@drawable/forced_resizable_background</item>
+ <item name="android:statusBarColor">@color/transparent</item>
+ <item name="android:windowAnimationStyle">@style/Animation.ForcedResizable</item>
+ </style>
+
+ <style name="Animation.ForcedResizable" parent="@android:style/Animation">
+ <item name="android:activityOpenEnterAnimation">@anim/forced_resizable_enter</item>
+ <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
+ </style>
+
<style name="TextAppearance.StatusBar.HeadsUp"
parent="@*android:style/TextAppearance.StatusBar">
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 1261a9a..9cba93b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -34,6 +34,7 @@
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
@@ -43,6 +44,7 @@
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
@@ -281,6 +283,8 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mSystemBroadcastReceiver, filter);
+
+ getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
}
@Override
@@ -602,6 +606,11 @@
mRecentsView.invalidate();
}
+ public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+ mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ mRecentsView.invalidate();
+ }
+
public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
int launchToTaskId = launchState.launchedToTaskId;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 3ff33a8..fce120a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -43,8 +43,10 @@
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.EventBus.Event;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
+import com.android.systemui.recents.events.activity.ForcedResizableEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
@@ -124,6 +126,13 @@
loader.loadTasks(mContext, plan, launchOpts);
}
}
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId) {
+ EventBus.getDefault().sendOntoMainThread(
+ new ForcedResizableEvent(packageName, taskId));
+
+ }
}
protected static RecentsTaskLoadPlan sInstanceLoadPlan;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppTransitionFinishedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppTransitionFinishedEvent.java
new file mode 100644
index 0000000..4738eed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppTransitionFinishedEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Sent when an app transition has finished playing.
+ */
+public class AppTransitionFinishedEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DockedFirstAnimationFrameEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DockedFirstAnimationFrameEvent.java
new file mode 100644
index 0000000..32d9a70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DockedFirstAnimationFrameEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * Sent when the window animation has started when docking a task
+ */
+public class DockedFirstAnimationFrameEvent extends Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ForcedResizableEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ForcedResizableEvent.java
new file mode 100644
index 0000000..cdcabf0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ForcedResizableEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Sent when recents received the information that an activity got forced resizable, and we need
+ * to inform the user about that.
+ */
+public class ForcedResizableEvent extends EventBus.Event {
+
+ public final String packageName;
+ public final int taskId;
+
+ public ForcedResizableEvent(String packageName, int taskId) {
+ this.packageName = packageName;
+ this.taskId = taskId;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index ea4888d..1e624fd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -52,6 +52,7 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -64,9 +65,8 @@
import android.util.Log;
import android.util.MutableBoolean;
import android.view.Display;
+import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
-import android.view.Surface;
-import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.KeyboardShortcutsReceiver;
import android.view.WindowManagerGlobal;
@@ -141,6 +141,7 @@
public void onActivityPinned() { }
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationEnded() { }
+ public void onActivityForcedResizable(String packageName, int taskId) { }
}
/**
@@ -172,10 +173,17 @@
mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
}
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId)
+ throws RemoteException {
+ mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, 0, packageName)
+ .sendToTarget();
+ }
};
/**
- * List of {@link TaskStackListener} registered from {@link registerTaskStackListener}.
+ * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}.
*/
private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
@@ -393,13 +401,12 @@
}
/** Docks a task to the side of the screen and starts it. */
- public void startTaskInDockedMode(Context context, View view, int taskId, int createMode) {
+ public void startTaskInDockedMode(int taskId, int createMode) {
if (mIam == null) return;
try {
// TODO: Determine what animation we want for the incoming task
- final ActivityOptions options = ActivityOptions.makeThumbnailAspectScaleUpAnimation(
- view, null, 0, 0, view.getWidth(), view.getHeight(), null, null);
+ final ActivityOptions options = ActivityOptions.makeBasic();
options.setDockCreateMode(createMode);
options.setLaunchStackId(DOCKED_STACK_ID);
mIam.startActivityFromRecents(taskId, options.toBundle());
@@ -1045,11 +1052,24 @@
}
}
+ public void overridePendingAppTransitionMultiThumbFuture(
+ IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
+ boolean scaleUp) {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
+ scaleUp);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to override transition: " + e);
+ }
+ }
+
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_ACTIVITY_PINNED = 2;
private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 3;
private static final int ON_PINNED_STACK_ANIMATION_ENDED = 4;
+ private static final int ON_ACTIVITY_FORCED_RESIZABLE = 5;
@Override
public void handleMessage(Message msg) {
@@ -1078,6 +1098,13 @@
}
break;
}
+ case ON_ACTIVITY_FORCED_RESIZABLE: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityForcedResizable(
+ (String) msg.obj, msg.arg1);
+ }
+ break;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 29da476..7c5b441 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -32,10 +33,8 @@
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
-import android.util.Log;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.recents.Recents;
@@ -52,6 +51,7 @@
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -92,7 +92,7 @@
*/
public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
final TaskStackView stackView, final TaskView taskView,
- final boolean screenPinningRequested, final Rect bounds, int destinationStack) {
+ final boolean screenPinningRequested, final Rect bounds, final int destinationStack) {
final ActivityOptions opts = ActivityOptions.makeBasic();
if (bounds != null) {
opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
@@ -101,7 +101,12 @@
final ActivityOptions.OnAnimationStartedListener animStartedListener;
final IAppTransitionAnimationSpecsFuture transitionFuture;
if (taskView != null) {
- transitionFuture = getAppTransitionFuture(task, stackView, destinationStack);
+ transitionFuture = getAppTransitionFuture(new AnimationSpecComposer() {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ return composeAnimationSpecs(task, stackView, destinationStack);
+ }
+ });
animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
@Override
public void onAnimationStarted() {
@@ -154,6 +159,23 @@
}
}
+ public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
+ if (listener == null) {
+ return null;
+ }
+ return new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onAnimationStarted();
+ }
+ });
+ }
+ };
+ }
+
/**
* Starts the activity for the launch task.
*
@@ -181,40 +203,21 @@
}
if (transitionFuture != null) {
- IRemoteCallback.Stub callback = null;
- if (animStartedListener != null) {
- callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (animStartedListener != null) {
- animStartedListener.onAnimationStarted();
- }
- }
- });
- }
- };
- }
- try {
- synchronized (this) {
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- }
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
- callback, true /* scaleUp */);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override transition: " + e);
- }
+ ssp.overridePendingAppTransitionMultiThumbFuture(transitionFuture,
+ wrapStartedListener(animStartedListener), true /* scaleUp */);
}
}
/**
* Creates a future which will later be queried for animation specs for this current transition.
+ *
+ * @param composer The implementation that composes the specs on the UI thread.
*/
- private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final Task task,
- final TaskStackView stackView, final int destinationStack) {
+ public IAppTransitionAnimationSpecsFuture getAppTransitionFuture(
+ final AnimationSpecComposer composer) {
+ synchronized (this) {
+ mAppTransitionAnimationSpecs = SPECS_WAITING;
+ }
return new IAppTransitionAnimationSpecsFuture.Stub() {
@Override
public AppTransitionAnimationSpec[] get() throws RemoteException {
@@ -222,8 +225,7 @@
@Override
public void run() {
synchronized (RecentsTransitionHelper.this) {
- mAppTransitionAnimationSpecs = composeAnimationSpecs(task, stackView,
- destinationStack);
+ mAppTransitionAnimationSpecs = composer.composeSpecs();
RecentsTransitionHelper.this.notifyAll();
}
}
@@ -248,6 +250,17 @@
}
/**
+ * Composes the transition spec when docking a task, which includes a full task bitmap.
+ */
+ public List<AppTransitionAnimationSpec> composeDockAnimationSpec(
+ TaskView taskView, Rect transform) {
+ TaskViewTransform viewTransform = new TaskViewTransform();
+ viewTransform.fillIn(taskView);
+ return Collections.singletonList(new AppTransitionAnimationSpec(taskView.getTask().key.id,
+ RecentsTransitionHelper.composeTaskBitmap(taskView, viewTransform), transform));
+ }
+
+ /**
* Composes the animation specs for all the tasks in the target stack.
*/
private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
@@ -319,6 +332,43 @@
return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
}
+ public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
+ float scale = transform.scale;
+ int fromWidth = (int) (transform.rect.width() * scale);
+ int fromHeight = (int) (transform.rect.height() * scale);
+ Bitmap b = Bitmap.createBitmap(fromWidth, fromHeight,
+ Bitmap.Config.ARGB_8888);
+
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ b.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(b);
+ c.scale(scale, scale);
+ taskView.draw(c);
+ c.setBitmap(null);
+ }
+ return b.createAshmemBitmap();
+ }
+
+ private static Bitmap composeHeaderBitmap(TaskView taskView,
+ TaskViewTransform transform) {
+ float scale = transform.scale;
+ int fromHeaderWidth = (int) (transform.rect.width());
+ int fromHeaderHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+ Bitmap b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
+ Bitmap.Config.ARGB_8888);
+
+ if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+ b.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(b);
+ c.scale(scale, scale);
+ taskView.mHeaderView.draw(c);
+ c.setBitmap(null);
+ }
+ return b.createAshmemBitmap();
+ }
+
/**
* Composes a single animation spec for the given {@link TaskView}
*/
@@ -326,21 +376,7 @@
TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
Bitmap b = null;
if (addHeaderBitmap) {
- float scale = transform.scale;
- int fromHeaderWidth = (int) (transform.rect.width());
- int fromHeaderHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
- b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
- Bitmap.Config.ARGB_8888);
-
- if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
- b.eraseColor(0xFFff0000);
- } else {
- Canvas c = new Canvas(b);
- c.scale(scale, scale);
- taskView.mHeaderView.draw(c);
- c.setBitmap(null);
- }
- b = b.createAshmemBitmap();
+ b = composeHeaderBitmap(taskView, transform);
}
Rect taskRect = new Rect();
@@ -351,4 +387,8 @@
}
return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
}
+
+ public interface AnimationSpecComposer {
+ List<AppTransitionAnimationSpec> composeSpecs();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 70c4c81..28880df 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -21,6 +21,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -28,9 +29,14 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
import android.util.ArraySet;
import android.util.AttributeSet;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -38,9 +44,11 @@
import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
+import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.Interpolators;
@@ -52,6 +60,7 @@
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
@@ -68,6 +77,7 @@
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
import com.android.systemui.stackdivider.WindowManagerProxy;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -484,31 +494,30 @@
event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
taskViewRect.right, taskViewRect.bottom);
- // Remove the task view after it is docked
- mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */);
- stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform,
- null);
- tmpTransform.alpha = 0;
- tmpTransform.scale = 1f;
- tmpTransform.rect.set(taskViewRect);
- mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
- new AnimationProps(125, Interpolators.ALPHA_OUT,
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Dock the task and launch it
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.startTaskInDockedMode(getContext(), event.taskView,
- event.task.key.id, dockState.createMode);
+ final OnAnimationStartedListener startedListener = new OnAnimationStartedListener() {
+ @Override
+ public void onAnimationStarted() {
+ EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
+ mTaskStackView.getStack().removeTask(event.task, AnimationProps.IMMEDIATE,
+ true /* fromDockGesture */);
+ }
+ };
- // Animate the stack accordingly
- AnimationProps stackAnim = new AnimationProps(
- TaskStackView.DEFAULT_SYNC_STACK_DURATION,
- Interpolators.FAST_OUT_SLOW_IN);
- mTaskStackView.getStack().removeTask(event.task, stackAnim,
- true /* fromDockGesture */);
- }
- }));
+ // Dock the task and launch it
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
+ final Rect taskRect = getTaskRect(event.taskView);
+ IAppTransitionAnimationSpecsFuture future = mTransitionHelper.getAppTransitionFuture(
+ new AnimationSpecComposer() {
+ @Override
+ public List<AppTransitionAnimationSpec> composeSpecs() {
+ return mTransitionHelper.composeDockAnimationSpec(
+ event.taskView, taskRect);
+ }
+ });
+ ssp.overridePendingAppTransitionMultiThumbFuture(future,
+ mTransitionHelper.wrapStartedListener(startedListener),
+ true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP);
} else {
@@ -518,6 +527,15 @@
}
}
+ private Rect getTaskRect(TaskView taskView) {
+ int[] location = taskView.getLocationOnScreen();
+ int viewX = location[0];
+ int viewY = location[1];
+ return new Rect(viewX, viewY,
+ (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
+ (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
+ }
+
public final void onBusEvent(DraggingInRecentsEvent event) {
if (mTaskStackView.getTaskViews().size() > 0) {
setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index c9cc1e9..bea47f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -109,7 +109,7 @@
public static final int DEFAULT_SYNC_STACK_DURATION = 200;
private static final int DRAG_SCALE_DURATION = 175;
- private static final float DRAG_SCALE_FACTOR = 1.05f;
+ static final float DRAG_SCALE_FACTOR = 1.05f;
private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216;
private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index dd59fac..e8cf126 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -41,6 +41,7 @@
private DockDividerVisibilityListener mDockDividerVisibilityListener;
private boolean mVisible = false;
private boolean mMinimized = false;
+ private ForcedResizableInfoActivityController mForcedResizableController;
@Override
public void start() {
@@ -52,6 +53,7 @@
mDockDividerVisibilityListener = new DockDividerVisibilityListener();
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.registerDockedStackListener(mDockDividerVisibilityListener);
+ mForcedResizableController = new ForcedResizableInfoActivityController(mContext);
}
@Override
@@ -117,6 +119,15 @@
});
}
+ private void notifyDockedStackExistsChanged(final boolean exists) {
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ mForcedResizableController.notifyDockedStackExistsChanged(exists);
+ }
+ });
+ }
+
class DockDividerVisibilityListener extends IDockedStackListener.Stub {
@Override
@@ -126,6 +137,7 @@
@Override
public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
+ notifyDockedStackExistsChanged(exists);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 132c09f..bab6baa 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -66,6 +66,8 @@
import com.android.systemui.recents.events.activity.UndockingTaskEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.stackdivider.events.StartedDragingEvent;
+import com.android.systemui.stackdivider.events.StoppedDragingEvent;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
@@ -281,6 +283,7 @@
mWindowManager.setSlippery(false);
liftBackground();
}
+ EventBus.getDefault().send(new StartedDragingEvent());
return mDockSide != WindowManager.DOCKED_INVALID;
}
@@ -439,6 +442,7 @@
mDockSide = WindowManager.DOCKED_INVALID;
mCurrentAnimator = null;
mEntranceAnimationRunning = false;
+ EventBus.getDefault().send(new StoppedDragingEvent());
}
});
mCurrentAnimator = anim;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java
new file mode 100644
index 0000000..f728dab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.stackdivider;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.android.systemui.R;
+
+/**
+ * Translucent activity that gets started on top of a task in multi-window to inform the user that
+ * we forced the activity below to be resizable.
+ */
+public class ForcedResizableInfoActivity extends Activity implements OnTouchListener {
+
+ private static final long DISMISS_DELAY = 2500;
+
+ private final Runnable mFinishRunnable = new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.forced_resizable_activity);
+ getWindow().getDecorView().setOnTouchListener(this);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ getWindow().getDecorView().postDelayed(mFinishRunnable, DISMISS_DELAY);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ finish();
+ return true;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ finish();
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
new file mode 100644
index 0000000..9b56037
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.stackdivider;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
+import com.android.systemui.recents.events.activity.ForcedResizableEvent;
+import com.android.systemui.stackdivider.events.StartedDragingEvent;
+import com.android.systemui.stackdivider.events.StoppedDragingEvent;
+
+/**
+ * Controller that decides when to show the {@link ForcedResizableInfoActivity}.
+ */
+public class ForcedResizableInfoActivityController {
+
+ private static final String SELF_PACKAGE_NAME = "com.android.systemui";
+
+ private static final int TIMEOUT = 1000;
+ private final Context mContext;
+ private final Handler mHandler = new Handler();
+ private final ArraySet<Integer> mPendingTaskIds = new ArraySet<>();
+ private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
+ private boolean mDividerDraging;
+
+ private final Runnable mTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ showPending();
+ }
+ };
+
+ public ForcedResizableInfoActivityController(Context context) {
+ mContext = context;
+ EventBus.getDefault().register(this);
+ }
+
+ public void notifyDockedStackExistsChanged(boolean exists) {
+ if (!exists) {
+ mPackagesShownInSession.clear();
+ }
+ }
+
+ public final void onBusEvent(ForcedResizableEvent forcedResizableEvent) {
+ if (debounce(forcedResizableEvent.packageName)) {
+ return;
+ }
+ mPendingTaskIds.add(forcedResizableEvent.taskId);
+ postTimeout();
+ }
+
+ public final void onBusEvent(AppTransitionFinishedEvent event) {
+ if (!mDividerDraging) {
+ showPending();
+ }
+ }
+
+ public final void onBusEvent(StartedDragingEvent event) {
+ mDividerDraging = true;
+ mHandler.removeCallbacks(mTimeoutRunnable);
+ }
+
+ public final void onBusEvent(StoppedDragingEvent event) {
+ mDividerDraging = false;
+ showPending();
+ }
+
+ private void showPending() {
+ mHandler.removeCallbacks(mTimeoutRunnable);
+ for (int i = mPendingTaskIds.size() - 1; i >= 0; i--) {
+ Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskId(mPendingTaskIds.valueAt(i));
+ mContext.startActivity(intent, options.toBundle());
+ }
+ mPendingTaskIds.clear();
+ }
+
+ private void postTimeout() {
+ mHandler.removeCallbacks(mTimeoutRunnable);
+ mHandler.postDelayed(mTimeoutRunnable, TIMEOUT);
+ }
+
+ private boolean debounce(String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+
+ // We launch ForcedResizableInfoActivity into a task that was forced resizable, so that
+ // triggers another notification. So ignore our own activity.
+ if (SELF_PACKAGE_NAME.equals(packageName)) {
+ return true;
+ }
+ boolean debounce = mPackagesShownInSession.contains(packageName);
+ mPackagesShownInSession.add(packageName);
+ return debounce;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/events/StartedDragingEvent.java b/packages/SystemUI/src/com/android/systemui/stackdivider/events/StartedDragingEvent.java
new file mode 100644
index 0000000..5d19851
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/events/StartedDragingEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.stackdivider.events;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Sent when the divider is being draged either manually or by an animation.
+ */
+public class StartedDragingEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/events/StoppedDragingEvent.java b/packages/SystemUI/src/com/android/systemui/stackdivider/events/StoppedDragingEvent.java
new file mode 100644
index 0000000..c50d6d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/events/StoppedDragingEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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 com.android.systemui.stackdivider.events;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Sent when the divider isn't draging anymore.
+ */
+public class StoppedDragingEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 0ffab5b..d00e8ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -73,6 +73,7 @@
private static final int MSG_REMOVE_QS_TILE = 28 << MSG_SHIFT;
private static final int MSG_CLICK_QS_TILE = 29 << MSG_SHIFT;
private static final int MSG_TOGGLE_APP_SPLIT_SCREEN = 30 << MSG_SHIFT;
+ private static final int MSG_APP_TRANSITION_FINISHED = 31 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -117,6 +118,7 @@
public void appTransitionPending();
public void appTransitionCancelled();
public void appTransitionStarting(long startTime, long duration);
+ public void appTransitionFinished();
public void showAssistDisclosure();
public void startAssist(Bundle args);
public void onCameraLaunchGestureDetected(int source);
@@ -324,6 +326,14 @@
}
}
+ @Override
+ public void appTransitionFinished() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_APP_TRANSITION_FINISHED);
+ mHandler.sendEmptyMessage(MSG_APP_TRANSITION_FINISHED);
+ }
+ }
+
public void showAssistDisclosure() {
synchronized (mLock) {
mHandler.removeMessages(MSG_ASSIST_DISCLOSURE);
@@ -452,6 +462,9 @@
Pair<Long, Long> data = (Pair<Long, Long>) msg.obj;
mCallbacks.appTransitionStarting(data.first, data.second);
break;
+ case MSG_APP_TRANSITION_FINISHED:
+ mCallbacks.appTransitionFinished();
+ break;
case MSG_ASSIST_DISCLOSURE:
mCallbacks.showAssistDisclosure();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 726aed3..4add3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -24,6 +24,7 @@
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 9b1f338..7ae87e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -120,6 +120,7 @@
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
import com.android.systemui.recents.events.activity.UndockingTaskEvent;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.stackdivider.WindowManagerProxy;
@@ -4310,6 +4311,7 @@
@Override
public void appTransitionCancelled() {
mIconController.appTransitionCancelled();
+ EventBus.getDefault().send(new AppTransitionFinishedEvent());
}
@Override
@@ -4326,6 +4328,11 @@
}
@Override
+ public void appTransitionFinished() {
+ EventBus.getDefault().send(new AppTransitionFinishedEvent());
+ }
+
+ @Override
public void onCameraLaunchGestureDetected(int source) {
mLastCameraLaunchSource = source;
if (mStartedGoingToSleep) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 2524e1a..f9bb5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -171,6 +171,10 @@
}
@Override
+ public void appTransitionFinished() {
+ }
+
+ @Override
public void onCameraLaunchGestureDetected(int source) {
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1abb5ff..3d13715 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -55,7 +55,6 @@
import com.android.server.pm.Installer;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
-import com.android.server.wm.AppTransition;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParser;
@@ -103,7 +102,6 @@
import android.app.ProfilerInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
-import android.app.admin.IDevicePolicyManager;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.IBackupManager;
@@ -355,6 +353,7 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
+import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
@@ -1471,6 +1470,7 @@
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 NOTIFY_FORCED_RESIZABLE_MSG = 67;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2019,6 +2019,21 @@
}
break;
}
+ case NOTIFY_FORCED_RESIZABLE_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).onActivityForcedResizable(
+ (String) msg.obj, msg.arg1);
+ } 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;
@@ -4479,63 +4494,14 @@
}
final long origId = Binder.clearCallingIdentity();
try {
- return startActivityFromRecentsInner(taskId, bOptions);
+ synchronized (this) {
+ return mStackSupervisor.startActivityFromRecentsInner(taskId, bOptions);
+ }
} finally {
Binder.restoreCallingIdentity(origId);
}
}
- final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
- final TaskRecord task;
- final int callingUid;
- final String callingPackage;
- final Intent intent;
- final int userId;
- synchronized (this) {
- final ActivityOptions activityOptions = (bOptions != null)
- ? new ActivityOptions(bOptions) : null;
- final int launchStackId = (activityOptions != null)
- ? activityOptions.getLaunchStackId() : INVALID_STACK_ID;
-
- if (launchStackId == HOME_STACK_ID) {
- throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
- + taskId + " can't be launch in the home stack.");
- }
- task = mStackSupervisor.anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId);
- if (task == null) {
- throw new IllegalArgumentException(
- "startActivityFromRecentsInner: Task " + taskId + " not found.");
- }
-
- if (launchStackId != INVALID_STACK_ID) {
- if (launchStackId == DOCKED_STACK_ID && activityOptions != null) {
- mWindowManager.setDockedStackCreateState(
- activityOptions.getDockCreateMode(), null /* initialBounds */);
- }
- if (task.stack.mStackId != launchStackId) {
- mStackSupervisor.moveTaskToStackLocked(
- taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
- ANIMATE);
- }
- }
-
- // If the user must confirm credentials (e.g. when first launching a work app and the
- // Work Challenge is present) let startActivityInPackage handle the intercepting.
- if (!mUserController.shouldConfirmCredentials(task.userId)
- && task.getRootActivity() != null) {
- moveTaskToFrontLocked(task.taskId, 0, bOptions);
- return ActivityManager.START_TASK_TO_FRONT;
- }
- callingUid = task.mCallingUid;
- callingPackage = task.mCallingPackage;
- intent = task.intent;
- intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
- userId = task.userId;
- }
- return startActivityInPackage(callingUid, callingPackage, intent, null, null, null, 0, 0,
- bOptions, userId, null, task);
- }
-
final int startActivityInPackage(int uid, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, Bundle bOptions, int userId,
@@ -11426,10 +11392,12 @@
// Get the focused task before launching launcher.
if (mUserController.isLockScreenDisabled(currentUserId)) {
+
// If there is no device lock, we will show the profile's credential page.
// startActivityFromRecentsInner is intercepted and will forward user to it.
if (mFocusedActivity != null) {
- startActivityFromRecentsInner(mFocusedActivity.task.taskId, null);
+ mStackSupervisor.startActivityFromRecentsInner(
+ mFocusedActivity.task.taskId, null);
}
} else {
// Showing launcher to avoid user entering credential twice.
@@ -21033,6 +21001,20 @@
mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reason);
}
}
+
+ @Override
+ public void notifyAppTransitionFinished() {
+ synchronized (ActivityManagerService.this) {
+ mStackSupervisor.notifyAppTransitionDone();
+ }
+ }
+
+ @Override
+ public void notifyAppTransitionCancelled() {
+ synchronized (ActivityManagerService.this) {
+ mStackSupervisor.notifyAppTransitionDone();
+ }
+ }
}
private final class SleepTokenImpl extends SleepToken {
@@ -21121,7 +21103,9 @@
// Will bring task to front if it already has a root activity.
final long origId = Binder.clearCallingIdentity();
try {
- startActivityFromRecentsInner(mTaskId, null);
+ synchronized (this) {
+ mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
+ }
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 5cb04c8..ee406da 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -258,6 +258,12 @@
// Current bounds of the stack or null if fullscreen.
Rect mBounds = null;
+ boolean mUpdateBoundsDeferred;
+ boolean mUpdateBoundsDeferredCalled;
+ final Rect mDeferredBounds = new Rect();
+ final Rect mDeferredTaskBounds = new Rect();
+ final Rect mDeferredTaskInsetBounds = new Rect();
+
long mLaunchStartTime = 0;
long mFullyDrawnStartTime = 0;
@@ -427,6 +433,57 @@
mActivityContainer.mActivityDisplay.mDisplay.getSize(out);
}
+ /**
+ * Defers updating the bounds of the stack. If the stack was resized/repositioned while
+ * deferring, the bounds will update in {@link #continueUpdateBounds()}.
+ */
+ void deferUpdateBounds() {
+ if (!mUpdateBoundsDeferred) {
+ mUpdateBoundsDeferred = true;
+ mUpdateBoundsDeferredCalled = false;
+ }
+ }
+
+ /**
+ * Continues updating bounds after updates have been deferred. If there was a resize attempt
+ * between {@link #deferUpdateBounds()} and {@link #continueUpdateBounds()}, the stack will
+ * be resized to that bounds.
+ */
+ void continueUpdateBounds() {
+ final boolean wasDeferred = mUpdateBoundsDeferred;
+ mUpdateBoundsDeferred = false;
+ if (wasDeferred && mUpdateBoundsDeferredCalled) {
+ mStackSupervisor.resizeStackUncheckedLocked(this,
+ mDeferredBounds.isEmpty() ? null : mDeferredBounds,
+ mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds,
+ mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds);
+ }
+ }
+
+ boolean updateBoundsAllowed(Rect bounds, Rect tempTaskBounds,
+ Rect tempTaskInsetBounds) {
+ if (!mUpdateBoundsDeferred) {
+ return true;
+ }
+ if (bounds != null) {
+ mDeferredBounds.set(bounds);
+ } else {
+ mDeferredBounds.setEmpty();
+ }
+ if (tempTaskBounds != null) {
+ mDeferredTaskBounds.set(tempTaskBounds);
+ } else {
+ mDeferredTaskBounds.setEmpty();
+ }
+ if (tempTaskInsetBounds != null) {
+ mDeferredTaskInsetBounds.set(tempTaskInsetBounds);
+ } else {
+ mDeferredTaskInsetBounds.setEmpty();
+ }
+ mUpdateBoundsDeferredCalled = true;
+ return false;
+ }
+
void setBounds(Rect bounds) {
mBounds = mFullscreen ? null : new Rect(bounds);
if (mTaskPositioner != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 7def1bd..0e6d174 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -52,6 +52,7 @@
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
@@ -101,7 +102,9 @@
import java.util.Objects;
import java.util.Set;
+import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.START_ANY_ACTIVITY;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
@@ -120,7 +123,6 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -151,6 +153,7 @@
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.ActivityManagerService.NOTIFY_FORCED_RESIZABLE_MSG;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
@@ -167,6 +170,7 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
+import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
public final class ActivityStackSupervisor implements DisplayListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
@@ -423,6 +427,11 @@
boolean mAppVisibilitiesChangedSinceLastPause;
/**
+ * Set of tasks that are in resizing mode during an app transition to fill the "void".
+ */
+ private final ArraySet<Integer> mResizingTasksDuringAnimation = new ArraySet<>();
+
+ /**
* Description of a request to start a new activity, which has been held
* due to app switches being disabled.
*/
@@ -1304,7 +1313,7 @@
boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
- ActivityRecord resultRecord, ActivityStack resultStack) {
+ ActivityRecord resultRecord, ActivityStack resultStack, ActivityOptions options) {
final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
callingUid);
if (startAnyPerm == PERMISSION_GRANTED) {
@@ -1358,6 +1367,19 @@
Slog.w(TAG, message);
return false;
}
+ if (options != null && options.getLaunchTaskId() != -1) {
+ final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
+ callingPid, callingUid);
+ if (startInTaskPerm != PERMISSION_GRANTED) {
+ final String msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ") with launchTaskId="
+ + options.getLaunchTaskId();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+
return true;
}
@@ -1788,6 +1810,8 @@
if (DEBUG_STACK) Slog.d(TAG_STACK,
"findTaskToMoveToFront: moved to front of stack=" + task.stack);
+
+ showNonResizeableDockToastIfNeeded(task, INVALID_STACK_ID, task.stack.mStackId);
}
boolean canUseActivityOptionsLaunchBounds(ActivityOptions options, int launchStackId) {
@@ -1924,10 +1948,39 @@
}
}
- private void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
+ void deferUpdateBounds(int stackId) {
+ final ActivityStack stack = getStack(stackId);
+ if (stack != null) {
+ stack.deferUpdateBounds();
+ }
+ }
+
+ void continueUpdateBounds(int stackId) {
+ final ActivityStack stack = getStack(stackId);
+ if (stack != null) {
+ stack.continueUpdateBounds();
+ }
+ }
+
+ void notifyAppTransitionDone() {
+ continueUpdateBounds(HOME_STACK_ID);
+ for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
+ final int taskId = mResizingTasksDuringAnimation.valueAt(i);
+ if (anyTaskForIdLocked(taskId) != null) {
+ mWindowManager.setTaskDockedResizing(taskId, false);
+ }
+ }
+ mResizingTasksDuringAnimation.clear();
+ }
+
+ void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
Rect tempTaskInsetBounds) {
bounds = TaskRecord.validateBounds(bounds);
+ if (!stack.updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
+ return;
+ }
+
mTmpBounds.clear();
mTmpConfigs.clear();
mTmpInsetBounds.clear();
@@ -3309,10 +3362,14 @@
return;
}
- if (!task.canGoInDockedStack() || task.mResizeMode == RESIZE_MODE_FORCE_RESIZEABLE) {
- // Display warning toast if we tried to put a non-dockable task in the docked stack or
- // the task was forced to be resizable by the system.
+ if (!task.canGoInDockedStack()) {
+ // Display a warning toast that we tried to put a non-dockable task in the docked stack.
mWindowManager.scheduleShowNonResizeableDockToast(task.taskId);
+ } else if (task.mResizeMode == RESIZE_MODE_FORCE_RESIZEABLE) {
+ String packageName = task.getTopActivity() != null
+ ? task.getTopActivity().appInfo.packageName : null;
+ mService.mHandler.obtainMessage(NOTIFY_FORCED_RESIZABLE_MSG, task.taskId, 0,
+ packageName).sendToTarget();
}
}
@@ -4129,4 +4186,80 @@
throw new IllegalStateException("Failed to find a stack behind stack=" + stack
+ " in=" + stacks);
}
+
+ /**
+ * Puts a task into resizing mode during the next app transition.
+ *
+ * @param taskId the id of the task to put into resizing mode
+ */
+ private void setResizingDuringAnimation(int taskId) {
+ mResizingTasksDuringAnimation.add(taskId);
+ mWindowManager.setTaskDockedResizing(taskId, true);
+ }
+
+ final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
+ final TaskRecord task;
+ final int callingUid;
+ final String callingPackage;
+ final Intent intent;
+ final int userId;
+ final ActivityOptions activityOptions = (bOptions != null)
+ ? new ActivityOptions(bOptions) : null;
+ final int launchStackId = (activityOptions != null)
+ ? activityOptions.getLaunchStackId() : INVALID_STACK_ID;
+
+ if (launchStackId == HOME_STACK_ID) {
+ throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+ + taskId + " can't be launch in the home stack.");
+ }
+ task = anyTaskForIdLocked(taskId, RESTORE_FROM_RECENTS, launchStackId);
+ if (task == null) {
+ throw new IllegalArgumentException(
+ "startActivityFromRecentsInner: Task " + taskId + " not found.");
+ }
+
+ if (launchStackId != INVALID_STACK_ID) {
+ if (launchStackId == DOCKED_STACK_ID) {
+ mWindowManager.setDockedStackCreateState(
+ activityOptions.getDockCreateMode(), null /* initialBounds */);
+
+ // Defer updating the stack in which recents is until the app transition is done, to
+ // not run into issues where we still need to draw the task in recents but the
+ // docked stack is already created.
+ deferUpdateBounds(HOME_STACK_ID);
+ mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
+ }
+ if (task.stack.mStackId != launchStackId) {
+ moveTaskToStackLocked(
+ taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
+ ANIMATE);
+ }
+ }
+
+ // If the user must confirm credentials (e.g. when first launching a work app and the
+ // Work Challenge is present) let startActivityInPackage handle the intercepting.
+ if (!mService.mUserController.shouldConfirmCredentials(task.userId)
+ && task.getRootActivity() != null) {
+ mService.moveTaskToFrontLocked(task.taskId, 0, bOptions);
+
+ // If we are launching the task in the docked stack, put it into resizing mode so
+ // the window renders full-screen with the background filling the void. Also only
+ // call this at the end to make sure that tasks exists on the window manager side.
+ if (launchStackId == DOCKED_STACK_ID) {
+ setResizingDuringAnimation(taskId);
+ }
+ return ActivityManager.START_TASK_TO_FRONT;
+ }
+ callingUid = task.mCallingUid;
+ callingPackage = task.mCallingPackage;
+ intent = task.intent;
+ intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+ userId = task.userId;
+ int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
+ null, null, 0, 0, bOptions, userId, null, task);
+ if (launchStackId == DOCKED_STACK_ID) {
+ setResizingDuringAnimation(task.taskId);
+ }
+ return result;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index af69c93..7be9f9d 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -362,7 +362,7 @@
boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
- resultRecord, resultStack);
+ resultRecord, resultStack, options);
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
@@ -547,12 +547,18 @@
}
if (startedActivityStackId == DOCKED_STACK_ID && prevFocusedStackId == HOME_STACK_ID) {
- // We launch an activity while being in home stack, which means either launcher or
- // recents into docked stack. We don't want the launched activity to be alone in a
- // docked stack, so we want to immediately launch recents too.
- if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
- mWindowManager.showRecentApps();
- return;
+ final ActivityStack homeStack = mSupervisor.getStack(HOME_STACK_ID);
+ final ActivityRecord topActivityHomeStack = homeStack != null
+ ? homeStack.topRunningActivityLocked() : null;
+ if (topActivityHomeStack == null
+ || topActivityHomeStack.mActivityType != RECENTS_ACTIVITY_TYPE) {
+ // We launch an activity while being in home stack, which means either launcher or
+ // recents into docked stack. We don't want the launched activity to be alone in a
+ // docked stack, so we want to immediately launch recents too.
+ if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
+ mWindowManager.showRecentApps();
+ return;
+ }
}
if (startedActivityStackId == PINNED_STACK_ID
@@ -879,6 +885,9 @@
ActivityRecord intentActivity = getReusableIntentActivity();
+ final int preferredLaunchStackId =
+ (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
+
if (intentActivity != null) {
// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
// still needs to be a lock task mode violation since the task gets cleared out and
@@ -938,6 +947,8 @@
// We didn't do anything... but it was needed (a.k.a., client don't use that
// intent!) And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetStackIfNeeded();
+ mSupervisor.showNonResizeableDockToastIfNeeded(mStartActivity.task,
+ preferredLaunchStackId, mTargetStack.mStackId);
return START_TASK_TO_FRONT;
}
}
@@ -977,6 +988,8 @@
}
top.deliverNewIntentLocked(
mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+ mSupervisor.showNonResizeableDockToastIfNeeded(mStartActivity.task,
+ preferredLaunchStackId, mTargetStack.mStackId);
return START_DELIVERED_TO_TOP;
}
@@ -1063,8 +1076,6 @@
}
mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
- final int preferredLaunchStackId =
- (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
mSupervisor.showNonResizeableDockToastIfNeeded(
mStartActivity.task, preferredLaunchStackId, mTargetStack.mStackId);
@@ -1297,7 +1308,10 @@
// same component, then instead of launching bring that one to the front.
putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
ActivityRecord intentActivity = null;
- if (putIntoExistingTask) {
+ if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
+ final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
+ intentActivity = task != null ? task.getTopActivity() : null;
+ } else if (putIntoExistingTask) {
// See if there is a task to bring to the front. If this is a SINGLE_INSTANCE
// activity, there can be one and only one instance of it in the history, and it is
// always in its own unique task, so we do a special search.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d92d942..1645366 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2778,8 +2778,10 @@
// If the divider is behind the navigation bar, don't animate.
if (mNavigationBar != null
- && (win.getFrameLw().top + insets >= mNavigationBar.getFrameLw().top
- || win.getFrameLw().left + insets >= mNavigationBar.getFrameLw().left)) {
+ && ((mNavigationBarOnBottom
+ && win.getFrameLw().top + insets >= mNavigationBar.getFrameLw().top)
+ || (!mNavigationBarOnBottom
+ && win.getFrameLw().left + insets >= mNavigationBar.getFrameLw().left))) {
return 0;
}
if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index 9d353c6..86d0468 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -29,6 +29,8 @@
import android.view.animation.TranslateAnimation;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
import static android.view.WindowManagerInternal.*;
@@ -103,6 +105,20 @@
}
});
}
+
+ @Override
+ public void onAppTransitionFinishedLocked(IBinder token) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ StatusBarManagerInternal statusbar = LocalServices.getService(
+ StatusBarManagerInternal.class);
+ if (statusbar != null) {
+ statusbar.appTransitionFinished();
+ }
+ }
+ });
+ }
};
public StatusBarController() {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 6bda4ed..9614417 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -34,4 +34,5 @@
void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask,
Rect fullscreenBounds, Rect dockedBounds, String cause);
void toggleSplitScreen();
+ void appTransitionFinished();
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 403a4c6..4a00ebd 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -214,6 +214,15 @@
} catch (RemoteException ex) {}
}
}
+
+ public void appTransitionFinished() {
+ enforceStatusBarService();
+ if (mBar != null) {
+ try {
+ mBar.appTransitionFinished();
+ } catch (RemoteException ex) {}
+ }
+ }
};
// ================================================================================
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 2a57d8e..62fa013 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -50,6 +50,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Path;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -76,6 +77,7 @@
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.animation.ClipRectLRAnimation;
import com.android.server.wm.animation.ClipRectTBAnimation;
+import com.android.server.wm.animation.CurvedTranslateAnimation;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -130,6 +132,8 @@
public static final int TRANSIT_TASK_IN_PLACE = 17;
/** An activity is being relaunched (e.g. due to configuration change). */
public static final int TRANSIT_ACTIVITY_RELAUNCH = 18;
+ /** A task is being docked from recents. */
+ public static final int TRANSIT_DOCK_TASK_FROM_RECENTS = 19;
/** Fraction of animation at which the recents thumbnail stays completely transparent */
private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f;
@@ -142,13 +146,15 @@
static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+ private static final Interpolator THUMBNAIL_DOCK_INTERPOLATOR =
+ new PathInterpolator(0.85f, 0f, 1f, 1f);
+
/**
* Maximum duration for the clip reveal animation. This is used when there is a lot of movement
* involved, to make it more understandable.
*/
private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
- private static final int THUMBNAIL_APP_TRANSITION_ALPHA_DURATION = 336;
private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
private final Context mContext;
@@ -209,6 +215,7 @@
private final Interpolator mThumbnailFadeOutInterpolator;
private final Interpolator mLinearOutSlowInInterpolator;
private final Interpolator mFastOutLinearInInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
private final int mClipRevealTranslationY;
@@ -229,6 +236,8 @@
com.android.internal.R.interpolator.linear_out_slow_in);
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_linear_in);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_slow_in);
mConfigShortAnimTime = context.getResources().getInteger(
com.android.internal.R.integer.config_shortAnimTime);
mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
@@ -815,12 +824,14 @@
* Prepares the specified animation with a standard duration, interpolator, etc.
*/
Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight,
- int duration, Interpolator interpolator) {
+ long duration, Interpolator interpolator) {
if (duration > 0) {
a.setDuration(duration);
}
a.setFillAfter(true);
- a.setInterpolator(interpolator);
+ if (interpolator != null) {
+ a.setInterpolator(interpolator);
+ }
a.initialize(appWidth, appHeight, appWidth, appHeight);
return a;
}
@@ -869,55 +880,77 @@
* This animation runs for the thumbnail that gets cross faded with the enter/exit activity
* when a thumbnail is specified with the pending animation override.
*/
- Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, Bitmap thumbnailHeader,
- final int taskId) {
+ Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
+ Bitmap thumbnailHeader, final int taskId) {
Animation a;
final int thumbWidthI = thumbnailHeader.getWidth();
final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
final int thumbHeightI = thumbnailHeader.getHeight();
- final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
final int appWidth = appRect.width();
float scaleW = appWidth / thumbWidth;
- float unscaledHeight = thumbHeight * scaleW;
getNextAppTransitionStartRect(taskId, mTmpRect);
- final float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f;
- final float toY = appRect.top + -unscaledStartY;
+ final float toY = mTmpRect.height() / 2 * (scaleW - 1f) + appRect.top;
+ final float fromY = mTmpRect.top;
+ final float toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
+ final float fromX = mTmpRect.left;
+ final float pivotX = mTmpRect.width() / 2;
+ final float pivotY = mTmpRect.height() / 2;
+ final long duration = getAspectScaleDuration();
+ final Interpolator interpolator = getAspectScaleInterpolator();
if (mNextAppTransitionScaleUp) {
// Animation up from the thumbnail to the full screen
- Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW,
- mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
- scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
+ scale.setInterpolator(interpolator);
+ scale.setDuration(duration);
Animation alpha = new AlphaAnimation(1f, 0f);
- alpha.setInterpolator(mThumbnailFadeOutInterpolator);
- alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
- final float toX = appRect.left + appRect.width() / 2 -
- (mTmpRect.left + thumbWidth / 2);
- Animation translate = new TranslateAnimation(0, toX, 0, toY);
- translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ alpha.setInterpolator(mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+ ? THUMBNAIL_DOCK_INTERPOLATOR : mThumbnailFadeOutInterpolator);
+ alpha.setDuration(mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
+ ? duration / 2
+ : duration);
+ Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
+ translate.setInterpolator(interpolator);
+ translate.setDuration(duration);
+
+ mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
+ mTmpToClipRect.set(appRect);
+
+ // Containing frame is in screen space, but we need the clip rect in the
+ // app space.
+ mTmpToClipRect.offsetTo(0, 0);
+ mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
+ mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
+
+ if (contentInsets != null) {
+ mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
+ (int) (-contentInsets.top * scaleW),
+ (int) (-contentInsets.right * scaleW),
+ (int) (-contentInsets.bottom * scaleW));
+ }
+
+ Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
+ clipAnim.setInterpolator(interpolator);
+ clipAnim.setDuration(duration);
// This AnimationSet uses the Interpolators assigned above.
AnimationSet set = new AnimationSet(false);
set.addAnimation(scale);
set.addAnimation(alpha);
set.addAnimation(translate);
+ set.addAnimation(clipAnim);
a = set;
} else {
// Animation down from the full screen to the thumbnail
- Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f,
- mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f));
- scale.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
+ scale.setInterpolator(interpolator);
+ scale.setDuration(duration);
Animation alpha = new AlphaAnimation(0f, 1f);
alpha.setInterpolator(mThumbnailFadeInInterpolator);
- alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION);
- final float toX = appRect.left + appRect.width() / 2 -
- (mTmpRect.left + thumbWidth / 2);
- Animation translate = new TranslateAnimation(toX, 0, toY, 0);
- translate.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
- translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION);
+ alpha.setDuration(duration);
+ Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
+ translate.setInterpolator(interpolator);
+ translate.setDuration(duration);
// This AnimationSet uses the Interpolators assigned above.
AnimationSet set = new AnimationSet(false);
@@ -928,7 +961,41 @@
}
return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
- TOUCH_RESPONSE_INTERPOLATOR);
+ null);
+ }
+
+ private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
+
+ // Almost no x-change - use linear animation
+ if (Math.abs(toX - fromX) < 1f || mNextAppTransition != TRANSIT_DOCK_TASK_FROM_RECENTS) {
+ return new TranslateAnimation(fromX, toX, fromY, toY);
+ } else {
+ final Path path = createCurvedPath(fromX, toX, fromY, toY);
+ return new CurvedTranslateAnimation(path);
+ }
+ }
+
+ private Path createCurvedPath(float fromX, float toX, float fromY, float toY) {
+ final Path path = new Path();
+ path.moveTo(fromX, fromY);
+ path.cubicTo(fromX, fromY, toX, 0.9f * fromY + 0.1f * toY, toX, toY);
+ return path;
+ }
+
+ private long getAspectScaleDuration() {
+ if (mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+ return (long) (THUMBNAIL_APP_TRANSITION_DURATION * 1.35f);
+ } else {
+ return THUMBNAIL_APP_TRANSITION_DURATION;
+ }
+ }
+
+ private Interpolator getAspectScaleInterpolator() {
+ if (mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+ return mFastOutSlowInInterpolator;
+ } else {
+ return TOUCH_RESPONSE_INTERPOLATOR;
+ }
}
/**
@@ -949,15 +1016,16 @@
final int thumbStartX = mTmpRect.left - containingFrame.left;
final int thumbStartY = mTmpRect.top - containingFrame.top;
- // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions
- float scale = 1f;
- int scaledTopDecor = 0;
-
switch (thumbTransitState) {
- case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
- if (freeform) {
+ case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
+ case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+ final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+ if (freeform && scaleUp) {
a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
containingFrame, surfaceInsets, taskId);
+ } else if (freeform) {
+ a = createAspectScaledThumbnailExitFreeformAnimationLocked(
+ containingFrame, surfaceInsets, taskId);
} else {
AnimationSet set = new AnimationSet(true);
@@ -976,16 +1044,33 @@
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
// We scale the width and clip to the top/left square
- scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
- scaledTopDecor = (int) (scale * contentInsets.top);
+ // We scale the width and clip to the top/left square
+ float scale = thumbWidth /
+ (appWidth - contentInsets.left - contentInsets.right);
int unscaledThumbHeight = (int) (thumbHeight / scale);
mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
- Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1,
- computePivot(mTmpRect.left - containingFrame.left, scale),
- computePivot(mTmpRect.top - containingFrame.top, scale));
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0);
+ mNextAppTransitionInsets.set(contentInsets);
+
+ Animation scaleAnim = new ScaleAnimation(
+ scaleUp ? scale : 1, scaleUp ? 1 : scale,
+ scaleUp ? scale : 1, scaleUp ? 1 : scale,
+ containingFrame.width() / 2f,
+ containingFrame.height() / 2f + contentInsets.top);
+ final float targetX = (mTmpRect.left - containingFrame.left);
+ final float x = containingFrame.width() / 2f
+ - containingFrame.width() / 2f * scale;
+ final float targetY = (mTmpRect.top - containingFrame.top);
+ final float y = containingFrame.height() / 2f
+ - containingFrame.height() / 2f * scale;
+ final float startX = targetX - x;
+ final float startY = targetY - y;
+ Animation clipAnim = scaleUp
+ ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
+ : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
+ Animation translateAnim = scaleUp
+ ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
+ : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
set.addAnimation(clipAnim);
set.addAnimation(scaleAnim);
@@ -996,14 +1081,18 @@
mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- Animation translateAnim = new TranslateAnimation(thumbStartX, 0,
- thumbStartY - contentInsets.top, 0);
+ Animation clipAnim = scaleUp
+ ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
+ : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
+ Animation translateAnim = scaleUp
+ ? createCurvedMotion(thumbStartX, 0,
+ thumbStartY - contentInsets.top, 0)
+ : createCurvedMotion(0, thumbStartX, 0,
+ thumbStartY - contentInsets.top);
set.addAnimation(clipAnim);
set.addAnimation(translateAnim);
}
-
a = set;
a.setZAdjustment(Animation.ZORDER_TOP);
}
@@ -1031,68 +1120,12 @@
}
break;
}
- case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
- // App window scaling down from full screen
- if (freeform) {
- a = createAspectScaledThumbnailExitFreeformAnimationLocked(
- containingFrame, surfaceInsets, taskId);
- } else {
- AnimationSet set = new AnimationSet(true);
- mTmpFromClipRect.set(containingFrame);
- mTmpToClipRect.set(containingFrame);
-
- // Containing frame is in screen space, but we need the clip rect in the
- // app space.
- mTmpFromClipRect.offsetTo(0, 0);
- mTmpToClipRect.offsetTo(0, 0);
-
- // Exclude insets region from the target clip.
- mTmpToClipRect.inset(contentInsets);
- mNextAppTransitionInsets.set(contentInsets);
-
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- // We scale the width and clip to the top/left square
- scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
- scaledTopDecor = (int) (scale * contentInsets.top);
- int unscaledThumbHeight = (int) (thumbHeight / scale);
- mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight;
-
- Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale,
- computePivot(mTmpRect.left - containingFrame.left, scale),
- computePivot(mTmpRect.top - containingFrame.top, scale));
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor);
-
- set.addAnimation(clipAnim);
- set.addAnimation(scaleAnim);
- set.addAnimation(translateAnim);
-
- } else {
- // In landscape, we don't scale at all and only crop
- mTmpToClipRect.bottom = mTmpToClipRect.top + thumbHeightI;
- mTmpToClipRect.right = mTmpToClipRect.left + thumbWidthI;
-
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- Animation translateAnim = new TranslateAnimation(0, thumbStartX, 0,
- thumbStartY - contentInsets.top);
-
- set.addAnimation(clipAnim);
- set.addAnimation(translateAnim);
- }
-
- a = set;
- a.setZAdjustment(Animation.ZORDER_TOP);
- }
- break;
- }
default:
throw new RuntimeException("Invalid thumbnail transition state");
}
- int duration = Math.max(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION,
- THUMBNAIL_APP_TRANSITION_DURATION);
- return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
- TOUCH_RESPONSE_INTERPOLATOR);
+ return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
+ getAspectScaleDuration(), getAspectScaleInterpolator());
}
private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
@@ -1446,6 +1479,7 @@
? WindowAnimation_activityCloseEnterAnimation
: WindowAnimation_activityCloseExitAnimation;
break;
+ case TRANSIT_DOCK_TASK_FROM_RECENTS:
case TRANSIT_TASK_OPEN:
animAttr = enter
? WindowAnimation_taskOpenEnterAnimation
@@ -1504,6 +1538,7 @@
int getAppStackClipMode() {
return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH
+ || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
? STACK_CLIP_NONE
: STACK_CLIP_AFTER_ANIM;
}
@@ -1727,6 +1762,9 @@
case TRANSIT_ACTIVITY_RELAUNCH: {
return "TRANSIT_ACTIVITY_RELAUNCH";
}
+ case TRANSIT_DOCK_TASK_FROM_RECENTS: {
+ return "TRANSIT_DOCK_TASK_FROM_RECENTS";
+ }
default: {
return "<UNKNOWN>";
}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 6225fc6..aae52e8 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -77,8 +77,6 @@
// requires that the duration of the two animations are the same.
SurfaceControl thumbnail;
int thumbnailTransactionSeq;
- int thumbnailX;
- int thumbnailY;
int thumbnailLayer;
int thumbnailForceAboveLayer;
Animation thumbnailAnimation;
@@ -254,7 +252,6 @@
thumbnailTransformation.clear();
final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
- thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY);
ScreenRotationAnimation screenRotationAnimation =
mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
@@ -288,6 +285,7 @@
}
thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+ thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
}
/**
@@ -461,8 +459,6 @@
}
if (thumbnail != null) {
pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
- pw.print(" x="); pw.print(thumbnailX);
- pw.print(" y="); pw.print(thumbnailY);
pw.print(" layer="); pw.println(thumbnailLayer);
pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
pw.print(prefix); pw.print("thumbnailTransformation=");
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
new file mode 100644
index 0000000..08acf9d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.wm;
+
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+
+/**
+ * Describes the mode in which a window is drag resizing.
+ */
+class DragResizeMode {
+
+ /**
+ * Freeform mode: Client surface is fullscreen, and client is responsible to draw window at
+ * the correct position.
+ */
+ static final int DRAG_RESIZE_MODE_FREEFORM = 0;
+
+ /**
+ * Mode for resizing the docked (and adjacent) stack: Client surface is fullscreen, but window
+ * is drawn at (0, 0), window manager is responsible for positioning the surface when draging.
+ */
+ static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+ static boolean isModeAllowedForStack(int stackId, int mode) {
+ switch (mode) {
+ case DRAG_RESIZE_MODE_FREEFORM:
+ return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
+ return stackId == DOCKED_STACK_ID
+ || stackId == FULLSCREEN_WORKSPACE_STACK_ID
+ || stackId == HOME_STACK_ID;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f097eb2..4e8f19e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,8 +18,6 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -103,6 +101,7 @@
// Whether the task is currently being drag-resized
private boolean mDragResizing;
+ private int mDragResizeMode;
private boolean mHomeTask;
@@ -140,41 +139,7 @@
final String text =
mService.mContext.getString(R.string.dock_non_resizeble_failed_to_dock_text);
mService.mH.obtainMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST, 0, 0, text).sendToTarget();
- return;
}
-
- final int dockSide = mStack.getDockSide();
- if (mResizeMode != RESIZE_MODE_FORCE_RESIZEABLE || dockSide == DOCKED_INVALID) {
- return;
- }
-
- int xOffset = 0;
- int yOffset = 0;
- mStack.getBounds(mTmpRect);
-
- if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
- // The toast was originally placed at the bottom and centered. To place it at the
- // bottom-center of the stack, we offset it horizontally by the diff between the center
- // of the stack bounds vs. the center of the screen.
- displayContent.getLogicalDisplayRect(mTmpRect2);
- xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
- } else if (dockSide == DOCKED_TOP) {
- // The toast was originally placed at the bottom and centered. To place it at the bottom
- // center of the top stack, we offset it vertically by the diff between the bottom of
- // the stack bounds vs. the bottom of the content rect.
- //
- // Note here we use the content rect instead of the display rect, as we want the toast's
- // distance to the dock divider (when it's placed at the top half) to be the same as
- // it's distance to the top of the navigation bar (when it's placed at the bottom).
-
- // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
- displayContent.getContentRect(mTmpRect2);
- yOffset = mTmpRect2.bottom - mTmpRect.bottom;
- }
- final String text =
- mService.mContext.getString(R.string.dock_forced_resizable);
- mService.mH.obtainMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST,
- xOffset, yOffset, text).sendToTarget();
}
void addAppToken(int addPos, AppWindowToken wtoken, int resizeMode, boolean homeTask) {
@@ -543,9 +508,14 @@
mStack.getDisplayContent().getLogicalDisplayRect(out);
}
- void setDragResizing(boolean dragResizing) {
+ void setDragResizing(boolean dragResizing, int dragResizeMode) {
if (mDragResizing != dragResizing) {
+ if (!DragResizeMode.isModeAllowedForStack(mStack.mStackId, dragResizeMode)) {
+ throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
+ + mStack.mStackId + " dragResizeMode=" + dragResizeMode);
+ }
mDragResizing = dragResizing;
+ mDragResizeMode = dragResizeMode;
resetDragResizingChangeReported();
}
}
@@ -564,6 +534,10 @@
return mDragResizing || (mStack != null && mStack.isDragResizing());
}
+ int getDragResizeMode() {
+ return mDragResizeMode;
+ }
+
void updateDisplayInfo(final DisplayContent displayContent) {
if (displayContent == null) {
return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 92701de..ae70aa8 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -29,6 +29,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
@@ -379,7 +380,7 @@
private void endDragLocked() {
mResizing = false;
- mTask.setDragResizing(false);
+ mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
}
/** Returns true if the move operation should be ended. */
@@ -409,7 +410,7 @@
bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
}
mWindowDragBounds.set(left, top, right, bottom);
- mTask.setDragResizing(true);
+ mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index e018a4e..f76f03f 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -227,6 +227,10 @@
w.mLayer = layer;
w.mWinAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() +
getSpecialWindowAnimLayerAdjustment(w);
+ if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0
+ && w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
+ w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
+ }
}
void dump(PrintWriter pw, String s) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index be1d8067..5771d69 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -202,6 +202,8 @@
import static android.view.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -799,7 +801,13 @@
= new WindowManagerInternal.AppTransitionListener() {
@Override
+ public void onAppTransitionCancelledLocked() {
+ mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_CANCELLED);
+ }
+
+ @Override
public void onAppTransitionFinishedLocked(IBinder token) {
+ mH.sendEmptyMessage(H.NOTIFY_APP_TRANSITION_FINISHED);
AppWindowToken atoken = findAppWindowToken(token);
if (atoken == null) {
return;
@@ -2921,9 +2929,9 @@
}
}
final boolean freeformResizing = win.isDragResizing()
- && win.getResizeMode() == WindowState.DRAG_RESIZE_MODE_FREEFORM;
+ && win.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
final boolean dockedResizing = win.isDragResizing()
- && win.getResizeMode() == WindowState.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+ && win.getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER;
result |= freeformResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
result |= dockedResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
if (win.isAnimatingWithSavedSurface()) {
@@ -4025,8 +4033,6 @@
wAppAnimator.thumbnail.destroy();
}
wAppAnimator.thumbnail = tAppAnimator.thumbnail;
- wAppAnimator.thumbnailX = tAppAnimator.thumbnailX;
- wAppAnimator.thumbnailY = tAppAnimator.thumbnailY;
wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
tAppAnimator.thumbnail = null;
@@ -4990,6 +4996,23 @@
}
}
+ /**
+ * Puts a specific task into docked drag resizing mode. See {@link DragResizeMode}.
+ *
+ * @param taskId The id of the task to put into drag resize mode.
+ * @param resizing Whether to put the task into drag resize mode.
+ */
+ public void setTaskDockedResizing(int taskId, boolean resizing) {
+ synchronized (mWindowMap) {
+ Task task = mTaskIdToTask.get(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("setTaskDockedResizing: taskId " + taskId
+ + " not found.");
+ }
+ task.setDragResizing(resizing, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+ }
+ }
+
public void scrollTask(int taskId, Rect bounds) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
@@ -7695,7 +7718,9 @@
public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
- public static final int NOTIFY_STARTING_WINDOW_DRAWN = 48;
+ public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48;
+ public static final int NOTIFY_APP_TRANSITION_FINISHED = 49;
+ public static final int NOTIFY_STARTING_WINDOW_DRAWN = 50;
/**
* Used to denote that an integer field in a message will not be used.
@@ -8276,6 +8301,14 @@
mAmInternal.notifyAppTransitionStarting(msg.arg1);
}
break;
+ case NOTIFY_APP_TRANSITION_CANCELLED: {
+ mAmInternal.notifyAppTransitionCancelled();
+ }
+ break;
+ case NOTIFY_APP_TRANSITION_FINISHED: {
+ mAmInternal.notifyAppTransitionFinished();
+ }
+ break;
case NOTIFY_STARTING_WINDOW_DRAWN: {
mAmInternal.notifyStartingWindowDrawn();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 38fda1b..a302006 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -88,6 +88,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
@@ -124,9 +126,6 @@
// to capture touch events in that area.
static final int RESIZE_HANDLE_WIDTH_IN_DP = 30;
- static final int DRAG_RESIZE_MODE_FREEFORM = 0;
- static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
-
static final boolean DEBUG_DISABLE_SAVING_SURFACES = false;
final WindowManagerService mService;
@@ -756,20 +755,10 @@
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
- if (isVisibleLw() || mWinAnimator.isAnimating()) {
- // We don't adjust the dock divider frame for reasons other than performance. The
- // real reason is that if it gets adjusted before it is shown for the first time,
- // it would get size (0, 0). This causes a problem when we finally show the dock
- // divider and try to draw to it. We do set the surface size at that moment to
- // the correct size, but it's too late for the Surface Flinger to make it
- // available for view rendering and as a result the renderer receives size 1, 1.
- // This way we just keep the divider at the original size and Surface Flinger
- // will return the correct value to the renderer.
- mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
- mContentFrame.set(mFrame);
- if (!mFrame.equals(mLastFrame)) {
- mMovedByResize = true;
- }
+ mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
+ mContentFrame.set(mFrame);
+ if (!mFrame.equals(mLastFrame)) {
+ mMovedByResize = true;
}
} else {
mContentFrame.set(Math.max(mContentFrame.left, frame.left),
@@ -1309,7 +1298,7 @@
*/
boolean hasMoved() {
return mHasSurface && (mContentChanged || mMovedByResize)
- && !mAnimatingExit && !mWinAnimator.mLastHidden && mService.okToDisplay()
+ && !mAnimatingExit && mService.okToDisplay()
&& (mFrame.top != mLastFrame.top || mFrame.left != mLastFrame.left)
&& (mAttachedWindow == null || !mAttachedWindow.hasMoved());
}
@@ -2258,7 +2247,7 @@
return mResizeMode;
}
- private boolean computeDragResizing() {
+ boolean computeDragResizing() {
final Task task = getTask();
if (task == null) {
return false;
@@ -2288,9 +2277,14 @@
return;
}
mDragResizing = resizing;
- mResizeMode = mDragResizing && mDisplayContent.mDividerControllerLocked.isResizing()
- ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
- : DRAG_RESIZE_MODE_FREEFORM;
+ final Task task = getTask();
+ if (task != null && task.isDragResizing()) {
+ mResizeMode = task.getDragResizeMode();
+ } else {
+ mResizeMode = mDragResizing && mDisplayContent.mDividerControllerLocked.isResizing()
+ ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
+ : DRAG_RESIZE_MODE_FREEFORM;
+ }
}
boolean isDragResizing() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 83b6104..1e103f0 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -20,6 +20,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -38,8 +40,6 @@
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowManagerService.localLOGV;
import static com.android.server.wm.WindowManagerService.logWithStack;
-import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.WindowState.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 909c5f2..5ff0613 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -19,6 +19,7 @@
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
@@ -31,13 +32,21 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerService.H.*;
-import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
-import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.DO_TRAVERSAL;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_ACTIVITY_DRAWN;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_APP_TRANSITION_STARTING;
+import static com.android.server.wm.WindowManagerService.H.NOTIFY_STARTING_WINDOW_DRAWN;
+import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
+import static com.android.server.wm.WindowManagerService.H.REPORT_WINDOWS_CHANGE;
+import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
+import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER;
+import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
@@ -688,7 +697,8 @@
w.getTask().mStack.isAdjustedForMinimizedDockedStack();
if ((w.mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
&& !w.isDragResizing() && !adjustedForMinimizedDockedStack
- && (task == null || !w.getTask().mStack.getFreezeMovementAnimations())) {
+ && (task == null || !w.getTask().mStack.getFreezeMovementAnimations())
+ && !w.mWinAnimator.mLastHidden) {
winAnimator.setMoveAnimation(left, top);
} else if (w.mAttrs.type == TYPE_DOCK_DIVIDER &&
displayContent.getDockedDividerController().isAdjustingForIme()) {
@@ -705,11 +715,11 @@
w.mClient.moved(left, top);
} catch (RemoteException e) {
}
+ w.mMovedByResize = false;
}
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
w.mContentChanged = false;
- w.mMovedByResize = false;
// Moved from updateWindowsAndWallpaperLocked().
if (w.mHasSurface) {
@@ -1224,6 +1234,10 @@
if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
}
+ if (mService.mAppTransition.getAppTransition()
+ == AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS) {
+ appAnimator.startProlongAnimation(PROLONG_ANIMATION_AT_START);
+ }
}
return topOpeningApp;
}
@@ -1562,12 +1576,13 @@
WindowState win = appToken.findMainWindow();
Rect appRect = win != null ? win.getContentFrameLw() :
new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+ Rect insets = win != null ? win.mContentInsets : null;
// For the new aspect-scaled transition, we want it to always show
// above the animating opening/closing window, and we want to
// synchronize its thumbnail surface with the surface for the
// open/close animation (only on the way down)
anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
- thumbnailHeader, taskId);
+ insets, thumbnailHeader, taskId);
openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
openingAppAnimator.deferThumbnailDestruction =
!mService.mAppTransition.isNextThumbnailTransitionScaleUp();
@@ -1582,8 +1597,6 @@
openingAppAnimator.thumbnailLayer = openingLayer;
openingAppAnimator.thumbnailAnimation = anim;
mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
- openingAppAnimator.thumbnailX = mTmpStartRect.left;
- openingAppAnimator.thumbnailY = mTmpStartRect.top;
} catch (Surface.OutOfResourcesException e) {
Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
+ dirty.width() + " h=" + dirty.height(), e);
diff --git a/services/core/java/com/android/server/wm/animation/CurvedTranslateAnimation.java b/services/core/java/com/android/server/wm/animation/CurvedTranslateAnimation.java
new file mode 100644
index 0000000..33ac2ff
--- /dev/null
+++ b/services/core/java/com/android/server/wm/animation/CurvedTranslateAnimation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.wm.animation;
+
+import android.animation.KeyframeSet;
+import android.animation.PathKeyframes;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+/**
+ * Translate animation which follows a curved path.
+ */
+public class CurvedTranslateAnimation extends Animation {
+
+ private final PathKeyframes mKeyframes;
+
+ public CurvedTranslateAnimation(Path path) {
+ mKeyframes = KeyframeSet.ofPath(path);
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ PointF location = (PointF) mKeyframes.getValue(interpolatedTime);
+ t.getMatrix().setTranslate(location.x, location.y);
+ }
+}