| /* |
| * Copyright (C) 2017 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.shared.system; |
| |
| import android.app.ActivityManager.RunningTaskInfo; |
| import android.app.ActivityTaskManager; |
| import android.app.TaskStackListener; |
| import android.content.ComponentName; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Trace; |
| import android.util.Log; |
| import android.window.TaskSnapshot; |
| |
| import com.android.internal.os.SomeArgs; |
| import com.android.systemui.shared.recents.model.ThumbnailData; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Tracks all the task stack listeners |
| */ |
| public class TaskStackChangeListeners { |
| |
| private static final String TAG = TaskStackChangeListeners.class.getSimpleName(); |
| private static final TaskStackChangeListeners INSTANCE = new TaskStackChangeListeners(); |
| |
| private final Impl mImpl; |
| |
| private TaskStackChangeListeners() { |
| mImpl = new Impl(Looper.getMainLooper()); |
| } |
| |
| public static TaskStackChangeListeners getInstance() { |
| return INSTANCE; |
| } |
| |
| /** |
| * Registers a task stack listener with the system. |
| * This should be called on the main thread. |
| */ |
| public void registerTaskStackListener(TaskStackChangeListener listener) { |
| synchronized (mImpl) { |
| mImpl.addListener(listener); |
| } |
| } |
| |
| /** |
| * Unregisters a task stack listener with the system. |
| * This should be called on the main thread. |
| */ |
| public void unregisterTaskStackListener(TaskStackChangeListener listener) { |
| synchronized (mImpl) { |
| mImpl.removeListener(listener); |
| } |
| } |
| |
| private static class Impl extends TaskStackListener implements Handler.Callback { |
| |
| private static final int ON_TASK_STACK_CHANGED = 1; |
| private static final int ON_TASK_SNAPSHOT_CHANGED = 2; |
| private static final int ON_ACTIVITY_PINNED = 3; |
| private static final int ON_ACTIVITY_RESTART_ATTEMPT = 4; |
| private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6; |
| private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7; |
| private static final int ON_TASK_PROFILE_LOCKED = 8; |
| private static final int ON_ACTIVITY_UNPINNED = 10; |
| private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11; |
| private static final int ON_TASK_CREATED = 12; |
| private static final int ON_TASK_REMOVED = 13; |
| private static final int ON_TASK_MOVED_TO_FRONT = 14; |
| private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 15; |
| private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 16; |
| private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 17; |
| private static final int ON_TASK_DISPLAY_CHANGED = 18; |
| private static final int ON_TASK_LIST_UPDATED = 19; |
| private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 20; |
| private static final int ON_TASK_DESCRIPTION_CHANGED = 21; |
| private static final int ON_ACTIVITY_ROTATION = 22; |
| private static final int ON_LOCK_TASK_MODE_CHANGED = 23; |
| |
| /** |
| * List of {@link TaskStackChangeListener} registered from {@link #addListener}. |
| */ |
| private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>(); |
| private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>(); |
| |
| private final Handler mHandler; |
| private boolean mRegistered; |
| |
| Impl(Looper looper) { |
| mHandler = new Handler(looper, this); |
| } |
| |
| public void addListener(TaskStackChangeListener listener) { |
| synchronized (mTaskStackListeners) { |
| mTaskStackListeners.add(listener); |
| } |
| if (!mRegistered) { |
| // Register mTaskStackListener to IActivityManager only once if needed. |
| try { |
| ActivityTaskManager.getService().registerTaskStackListener(this); |
| mRegistered = true; |
| } catch (Exception e) { |
| Log.w(TAG, "Failed to call registerTaskStackListener", e); |
| } |
| } |
| } |
| |
| public void removeListener(TaskStackChangeListener listener) { |
| boolean isEmpty; |
| synchronized (mTaskStackListeners) { |
| mTaskStackListeners.remove(listener); |
| isEmpty = mTaskStackListeners.isEmpty(); |
| } |
| if (isEmpty && mRegistered) { |
| // Unregister mTaskStackListener once we have no more listeners |
| try { |
| ActivityTaskManager.getService().unregisterTaskStackListener(this); |
| mRegistered = false; |
| } catch (Exception e) { |
| Log.w(TAG, "Failed to call unregisterTaskStackListener", e); |
| } |
| } |
| } |
| |
| @Override |
| public void onTaskStackChanged() { |
| // Call the task changed callback for the non-ui thread listeners first. Copy to a set |
| // of temp listeners so that we don't lock on mTaskStackListeners while calling all the |
| // callbacks. This call is always on the same binder thread, so we can just synchronize |
| // on the copying of the listener list. |
| synchronized (mTaskStackListeners) { |
| mTmpListeners.addAll(mTaskStackListeners); |
| } |
| for (int i = mTmpListeners.size() - 1; i >= 0; i--) { |
| mTmpListeners.get(i).onTaskStackChangedBackground(); |
| } |
| mTmpListeners.clear(); |
| |
| mHandler.removeMessages(ON_TASK_STACK_CHANGED); |
| mHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED); |
| } |
| |
| @Override |
| public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { |
| mHandler.removeMessages(ON_ACTIVITY_PINNED); |
| mHandler.obtainMessage(ON_ACTIVITY_PINNED, |
| new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityUnpinned() { |
| mHandler.removeMessages(ON_ACTIVITY_UNPINNED); |
| mHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED); |
| } |
| |
| @Override |
| public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible, |
| boolean clearedTask, boolean wasVisible) { |
| final SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = task; |
| args.argi1 = homeTaskVisible ? 1 : 0; |
| args.argi2 = clearedTask ? 1 : 0; |
| args.argi3 = wasVisible ? 1 : 0; |
| mHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT); |
| mHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityForcedResizable(String packageName, int taskId, int reason) { |
| mHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityDismissingDockedTask() { |
| mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); |
| } |
| |
| @Override |
| public void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo, |
| int requestedDisplayId) { |
| mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED, |
| requestedDisplayId, |
| 0 /* unused */, |
| taskInfo).sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo, |
| int requestedDisplayId) { |
| mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED, |
| requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskProfileLocked(int taskId, int userId) { |
| mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { |
| mHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskCreated(int taskId, ComponentName componentName) { |
| mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskRemoved(int taskId) { |
| mHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskMovedToFront(RunningTaskInfo taskInfo) { |
| mHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget(); |
| } |
| |
| @Override |
| public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { |
| mHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { |
| mHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId, |
| requestedOrientation).sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskDisplayChanged(int taskId, int newDisplayId) { |
| mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget(); |
| } |
| |
| @Override |
| public void onRecentTaskListUpdated() { |
| mHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget(); |
| } |
| |
| @Override |
| public void onRecentTaskListFrozenChanged(boolean frozen) { |
| mHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) { |
| mHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget(); |
| } |
| |
| @Override |
| public void onActivityRotation(int displayId) { |
| mHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onLockTaskModeChanged(int mode) { |
| mHandler.obtainMessage(ON_LOCK_TASK_MODE_CHANGED, mode, 0 /* unused */).sendToTarget(); |
| } |
| |
| @Override |
| public boolean handleMessage(Message msg) { |
| synchronized (mTaskStackListeners) { |
| switch (msg.what) { |
| case ON_TASK_STACK_CHANGED: { |
| Trace.beginSection("onTaskStackChanged"); |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskStackChanged(); |
| } |
| Trace.endSection(); |
| break; |
| } |
| case ON_TASK_SNAPSHOT_CHANGED: { |
| Trace.beginSection("onTaskSnapshotChanged"); |
| final TaskSnapshot snapshot = (TaskSnapshot) msg.obj; |
| final ThumbnailData thumbnail = new ThumbnailData(snapshot); |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, thumbnail); |
| } |
| Trace.endSection(); |
| break; |
| } |
| case ON_ACTIVITY_PINNED: { |
| final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj; |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityPinned( |
| info.mPackageName, info.mUserId, info.mTaskId, |
| info.mStackId); |
| } |
| break; |
| } |
| case ON_ACTIVITY_UNPINNED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityUnpinned(); |
| } |
| break; |
| } |
| case ON_ACTIVITY_RESTART_ATTEMPT: { |
| final SomeArgs args = (SomeArgs) msg.obj; |
| final RunningTaskInfo task = (RunningTaskInfo) args.arg1; |
| final boolean homeTaskVisible = args.argi1 != 0; |
| final boolean clearedTask = args.argi2 != 0; |
| final boolean wasVisible = args.argi3 != 0; |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityRestartAttempt(task, |
| homeTaskVisible, clearedTask, wasVisible); |
| } |
| break; |
| } |
| case ON_ACTIVITY_FORCED_RESIZABLE: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityForcedResizable( |
| (String) msg.obj, msg.arg1, msg.arg2); |
| } |
| break; |
| } |
| case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityDismissingDockedStack(); |
| } |
| break; |
| } |
| case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: { |
| final RunningTaskInfo info = (RunningTaskInfo) msg.obj; |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i) |
| .onActivityLaunchOnSecondaryDisplayFailed(info); |
| } |
| break; |
| } |
| case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED: { |
| final RunningTaskInfo info = (RunningTaskInfo) msg.obj; |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i) |
| .onActivityLaunchOnSecondaryDisplayRerouted(info); |
| } |
| break; |
| } |
| case ON_TASK_PROFILE_LOCKED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); |
| } |
| break; |
| } |
| case ON_TASK_CREATED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskCreated(msg.arg1, |
| (ComponentName) msg.obj); |
| } |
| break; |
| } |
| case ON_TASK_REMOVED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskRemoved(msg.arg1); |
| } |
| break; |
| } |
| case ON_TASK_MOVED_TO_FRONT: { |
| final RunningTaskInfo info = (RunningTaskInfo) msg.obj; |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskMovedToFront(info); |
| } |
| break; |
| } |
| case ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i) |
| .onActivityRequestedOrientationChanged(msg.arg1, msg.arg2); |
| } |
| break; |
| } |
| case ON_BACK_PRESSED_ON_TASK_ROOT: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onBackPressedOnTaskRoot( |
| (RunningTaskInfo) msg.obj); |
| } |
| break; |
| } |
| case ON_TASK_DISPLAY_CHANGED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskDisplayChanged(msg.arg1, msg.arg2); |
| } |
| break; |
| } |
| case ON_TASK_LIST_UPDATED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onRecentTaskListUpdated(); |
| } |
| break; |
| } |
| case ON_TASK_LIST_FROZEN_UNFROZEN: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onRecentTaskListFrozenChanged( |
| msg.arg1 != 0); |
| } |
| break; |
| } |
| case ON_TASK_DESCRIPTION_CHANGED: { |
| final RunningTaskInfo info = (RunningTaskInfo) msg.obj; |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onTaskDescriptionChanged(info); |
| } |
| break; |
| } |
| case ON_ACTIVITY_ROTATION: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onActivityRotation(msg.arg1); |
| } |
| break; |
| } |
| case ON_LOCK_TASK_MODE_CHANGED: { |
| for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { |
| mTaskStackListeners.get(i).onLockTaskModeChanged(msg.arg1); |
| } |
| break; |
| } |
| } |
| } |
| if (msg.obj instanceof SomeArgs) { |
| ((SomeArgs) msg.obj).recycle(); |
| } |
| return true; |
| } |
| } |
| |
| private static class PinnedActivityInfo { |
| final String mPackageName; |
| final int mUserId; |
| final int mTaskId; |
| final int mStackId; |
| |
| PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) { |
| mPackageName = packageName; |
| mUserId = userId; |
| mTaskId = taskId; |
| mStackId = stackId; |
| } |
| } |
| } |