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
*/
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[];
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);
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;
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);
@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."
* 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);
}
*/
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);
</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"
--- /dev/null
+<?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" />
--- /dev/null
+<?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
--- /dev/null
+<?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
<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>
<!-- 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>
<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>
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;
loader.loadTasks(mContext, plan, launchOpts);
}
}
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId) {
+ EventBus.getDefault().sendOntoMainThread(
+ new ForcedResizableEvent(packageName, taskId));
+
+ }
}
protected static RecentsTaskLoadPlan sInstanceLoadPlan;
--- /dev/null
+/*
+ * 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 {
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
public void onActivityPinned() { }
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationEnded() { }
+ public void onActivityForcedResizable(String packageName, int taskId) { }
}
/**
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<>();
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) {
}
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;
+ }
}
}
}
private DockDividerVisibilityListener mDockDividerVisibilityListener;
private boolean mVisible = false;
private boolean mMinimized = false;
+ private ForcedResizableInfoActivityController mForcedResizableController;
@Override
public void start() {
mDockDividerVisibilityListener = new DockDividerVisibilityListener();
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.registerDockedStackListener(mDockDividerVisibilityListener);
+ mForcedResizableController = new ForcedResizableInfoActivityController(mContext);
}
@Override
});
}
+ private void notifyDockedStackExistsChanged(final boolean exists) {
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ mForcedResizableController.notifyDockedStackExistsChanged(exists);
+ }
+ });
+ }
+
class DockDividerVisibilityListener extends IDockedStackListener.Stub {
@Override
@Override
public void onDockedStackExistsChanged(boolean exists) throws RemoteException {
+ notifyDockedStackExistsChanged(exists);
}
@Override
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;
mWindowManager.setSlippery(false);
liftBackground();
}
+ EventBus.getDefault().send(new StartedDragingEvent());
return mDockSide != WindowManager.DOCKED_INVALID;
}
mDockSide = WindowManager.DOCKED_INVALID;
mCurrentAnimator = null;
mEntranceAnimationRunning = false;
+ EventBus.getDefault().send(new StoppedDragingEvent());
}
});
mCurrentAnimator = anim;
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 {
+}
--- /dev/null
+/*
+ * 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 {
+}
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;
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);
}
}
+ @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);
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;
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;
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;
@Override
public void appTransitionCancelled() {
mIconController.appTransitionCancelled();
+ EventBus.getDefault().send(new AppTransitionFinishedEvent());
}
@Override
}
@Override
+ public void appTransitionFinished() {
+ EventBus.getDefault().send(new AppTransitionFinishedEvent());
+ }
+
+ @Override
public void onCameraLaunchGestureDetected(int source) {
mLastCameraLaunchSource = source;
if (mStartedGoingToSleep) {
}
@Override
+ public void appTransitionFinished() {
+ }
+
+ @Override
public void onCameraLaunchGestureDetected(int source) {
}
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;
}
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;
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;
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;
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) {
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;
}
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) {
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();
}
}
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);
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
// 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;
}
}
}
top.deliverNewIntentLocked(
mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+ mSupervisor.showNonResizeableDockToastIfNeeded(mStartActivity.task,
+ preferredLaunchStackId, mTargetStack.mStackId);
return START_DELIVERED_TO_TOP;
}
}
mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
- final int preferredLaunchStackId =
- (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
mSupervisor.showNonResizeableDockToastIfNeeded(
mStartActivity.task, preferredLaunchStackId, mTargetStack.mStackId);
// 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.
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.*;
}
});
}
+
+ @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() {
void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask,
Rect fullscreenBounds, Rect dockedBounds, String cause);
void toggleSplitScreen();
+ void appTransitionFinished();
}
} catch (RemoteException ex) {}
}
}
+
+ public void appTransitionFinished() {
+ enforceStatusBarService();
+ if (mBar != null) {
+ try {
+ mBar.appTransitionFinished();
+ } catch (RemoteException ex) {}
+ }
+ }
};
// ================================================================================
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) {