| /* |
| * Copyright (C) 2018 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityManager.TaskDescription; |
| import android.app.ActivityTaskManager; |
| import android.app.IActivityManager; |
| import android.app.ITaskStackListener; |
| import android.app.Instrumentation.ActivityMonitor; |
| import android.app.TaskStackListener; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.os.RemoteException; |
| import android.platform.test.annotations.Presubmit; |
| import android.support.test.uiautomator.UiDevice; |
| import android.text.TextUtils; |
| |
| import androidx.test.filters.FlakyTest; |
| import androidx.test.filters.MediumTest; |
| |
| import com.android.internal.annotations.GuardedBy; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Build/Install/Run: |
| * atest WmTests:TaskStackChangedListenerTest |
| */ |
| @MediumTest |
| public class TaskStackChangedListenerTest { |
| |
| private IActivityManager mService; |
| private ITaskStackListener mTaskStackListener; |
| |
| private static final Object sLock = new Object(); |
| @GuardedBy("sLock") |
| private static boolean sTaskStackChangedCalled; |
| private static boolean sActivityBResumed; |
| |
| @Before |
| public void setUp() throws Exception { |
| mService = ActivityManager.getService(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener); |
| mTaskStackListener = null; |
| } |
| |
| @Test |
| @Presubmit |
| public void testTaskStackChanged_afterFinish() throws Exception { |
| registerTaskStackChangedListener(new TaskStackListener() { |
| @Override |
| public void onTaskStackChanged() throws RemoteException { |
| synchronized (sLock) { |
| sTaskStackChangedCalled = true; |
| } |
| } |
| }); |
| |
| Context context = getInstrumentation().getContext(); |
| context.startActivity( |
| new Intent(context, ActivityA.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); |
| UiDevice.getInstance(getInstrumentation()).waitForIdle(); |
| synchronized (sLock) { |
| assertTrue(sTaskStackChangedCalled); |
| } |
| assertTrue(sActivityBResumed); |
| } |
| |
| @Test |
| @Presubmit |
| public void testTaskDescriptionChanged() throws Exception { |
| final Object[] params = new Object[2]; |
| final CountDownLatch latch = new CountDownLatch(1); |
| registerTaskStackChangedListener(new TaskStackListener() { |
| int mTaskId = -1; |
| |
| @Override |
| public void onTaskCreated(int taskId, ComponentName componentName) |
| throws RemoteException { |
| mTaskId = taskId; |
| } |
| @Override |
| public void onTaskDescriptionChanged(int taskId, TaskDescription td) |
| throws RemoteException { |
| if (mTaskId == taskId && !TextUtils.isEmpty(td.getLabel())) { |
| params[0] = taskId; |
| params[1] = td; |
| latch.countDown(); |
| } |
| } |
| }); |
| |
| int taskId; |
| synchronized (sLock) { |
| taskId = startTestActivity(ActivityTaskDescriptionChange.class).getTaskId(); |
| } |
| waitForCallback(latch); |
| assertEquals(taskId, params[0]); |
| assertEquals("Test Label", ((TaskDescription) params[1]).getLabel()); |
| } |
| |
| @Test |
| @Presubmit |
| public void testActivityRequestedOrientationChanged() throws Exception { |
| final int[] params = new int[2]; |
| final CountDownLatch latch = new CountDownLatch(1); |
| registerTaskStackChangedListener(new TaskStackListener() { |
| @Override |
| public void onActivityRequestedOrientationChanged(int taskId, |
| int requestedOrientation) { |
| params[0] = taskId; |
| params[1] = requestedOrientation; |
| latch.countDown(); |
| } |
| }); |
| int taskId; |
| synchronized (sLock) { |
| taskId = startTestActivity(ActivityRequestedOrientationChange.class).getTaskId(); |
| } |
| waitForCallback(latch); |
| assertEquals(taskId, params[0]); |
| assertEquals(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, params[1]); |
| } |
| |
| /** |
| * Tests for onTaskCreated, onTaskMovedToFront, onTaskRemoved and onTaskRemovalStarted. |
| */ |
| @Test |
| public void testTaskChangeCallBacks() throws Exception { |
| final Object[] params = new Object[2]; |
| final CountDownLatch taskCreatedLaunchLatch = new CountDownLatch(1); |
| final CountDownLatch taskMovedToFrontLatch = new CountDownLatch(1); |
| final CountDownLatch taskRemovedLatch = new CountDownLatch(1); |
| final CountDownLatch taskRemovalStartedLatch = new CountDownLatch(1); |
| final CountDownLatch onDetachedFromWindowLatch = new CountDownLatch(1); |
| registerTaskStackChangedListener(new TaskStackListener() { |
| @Override |
| public void onTaskCreated(int taskId, ComponentName componentName) |
| throws RemoteException { |
| params[0] = taskId; |
| params[1] = componentName; |
| taskCreatedLaunchLatch.countDown(); |
| } |
| |
| @Override |
| public void onTaskMovedToFront(int taskId) throws RemoteException { |
| params[0] = taskId; |
| taskMovedToFrontLatch.countDown(); |
| } |
| |
| @Override |
| public void onTaskRemovalStarted(int taskId) { |
| params[0] = taskId; |
| taskRemovalStartedLatch.countDown(); |
| } |
| |
| @Override |
| public void onTaskRemoved(int taskId) throws RemoteException { |
| params[0] = taskId; |
| taskRemovedLatch.countDown(); |
| } |
| }); |
| |
| final ActivityTaskChangeCallbacks activity = |
| (ActivityTaskChangeCallbacks) startTestActivity(ActivityTaskChangeCallbacks.class); |
| activity.setDetachedFromWindowLatch(onDetachedFromWindowLatch); |
| final int id = activity.getTaskId(); |
| |
| // Test for onTaskCreated. |
| waitForCallback(taskCreatedLaunchLatch); |
| assertEquals(id, params[0]); |
| ComponentName componentName = (ComponentName) params[1]; |
| assertEquals(ActivityTaskChangeCallbacks.class.getName(), componentName.getClassName()); |
| |
| // Test for onTaskMovedToFront. |
| assertEquals(1, taskMovedToFrontLatch.getCount()); |
| mService.moveTaskToFront(id, 0, null); |
| waitForCallback(taskMovedToFrontLatch); |
| assertEquals(activity.getTaskId(), params[0]); |
| |
| // Test for onTaskRemovalStarted. |
| assertEquals(1, taskRemovalStartedLatch.getCount()); |
| assertEquals(1, taskRemovedLatch.getCount()); |
| activity.finishAndRemoveTask(); |
| waitForCallback(taskRemovalStartedLatch); |
| // onTaskRemovalStarted happens before the activity's window is removed. |
| assertFalse(activity.mOnDetachedFromWindowCalled); |
| assertEquals(id, params[0]); |
| |
| // Test for onTaskRemoved. |
| waitForCallback(taskRemovedLatch); |
| assertEquals(id, params[0]); |
| waitForCallback(onDetachedFromWindowLatch); |
| assertTrue(activity.mOnDetachedFromWindowCalled); |
| } |
| |
| /** |
| * Starts the provided activity and returns the started instance. |
| */ |
| private TestActivity startTestActivity(Class<?> activityClass) throws InterruptedException { |
| final ActivityMonitor monitor = new ActivityMonitor(activityClass.getName(), null, false); |
| getInstrumentation().addMonitor(monitor); |
| final Context context = getInstrumentation().getContext(); |
| context.startActivity( |
| new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); |
| final TestActivity activity = (TestActivity) monitor.waitForActivityWithTimeout(1000); |
| if (activity == null) { |
| throw new RuntimeException("Timed out waiting for Activity"); |
| } |
| activity.waitForResumeStateChange(true); |
| return activity; |
| } |
| |
| private void registerTaskStackChangedListener(ITaskStackListener listener) throws Exception { |
| mTaskStackListener = listener; |
| ActivityTaskManager.getService().registerTaskStackListener(listener); |
| } |
| |
| private void waitForCallback(CountDownLatch latch) { |
| try { |
| final boolean result = latch.await(4, TimeUnit.SECONDS); |
| if (!result) { |
| throw new RuntimeException("Timed out waiting for task stack change notification"); |
| } |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| public static class TestActivity extends Activity { |
| boolean mIsResumed = false; |
| |
| @Override |
| protected void onPostResume() { |
| super.onPostResume(); |
| synchronized (this) { |
| mIsResumed = true; |
| notifyAll(); |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| synchronized (this) { |
| mIsResumed = false; |
| notifyAll(); |
| } |
| } |
| |
| /** |
| * If isResumed is {@code true}, sleep the thread until the activity is resumed. |
| * if {@code false}, sleep the thread until the activity is paused. |
| */ |
| @SuppressWarnings("WaitNotInLoop") |
| public void waitForResumeStateChange(boolean isResumed) throws InterruptedException { |
| synchronized (this) { |
| if (mIsResumed == isResumed) { |
| return; |
| } |
| wait(5000); |
| } |
| assertEquals("The activity resume state change timed out", isResumed, mIsResumed); |
| } |
| } |
| |
| public static class ActivityA extends TestActivity { |
| |
| private boolean mActivityBLaunched = false; |
| |
| @Override |
| protected void onPostResume() { |
| super.onPostResume(); |
| if (mActivityBLaunched) { |
| return; |
| } |
| mActivityBLaunched = true; |
| finish(); |
| startActivity(new Intent(this, ActivityB.class)); |
| } |
| } |
| |
| public static class ActivityB extends TestActivity { |
| |
| @Override |
| protected void onPostResume() { |
| super.onPostResume(); |
| synchronized (sLock) { |
| sTaskStackChangedCalled = false; |
| } |
| sActivityBResumed = true; |
| finish(); |
| } |
| } |
| |
| public static class ActivityRequestedOrientationChange extends TestActivity { |
| @Override |
| protected void onPostResume() { |
| super.onPostResume(); |
| setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
| synchronized (sLock) { |
| // Hold the lock to ensure no one is trying to access fields of this Activity in |
| // this test. |
| finish(); |
| } |
| } |
| } |
| |
| public static class ActivityTaskDescriptionChange extends TestActivity { |
| @Override |
| protected void onPostResume() { |
| super.onPostResume(); |
| setTaskDescription(new TaskDescription("Test Label")); |
| synchronized (sLock) { |
| // Hold the lock to ensure no one is trying to access fields of this Activity in |
| // this test. |
| finish(); |
| } |
| } |
| } |
| |
| public static class ActivityTaskChangeCallbacks extends TestActivity { |
| public boolean mOnDetachedFromWindowCalled = false; |
| private CountDownLatch mOnDetachedFromWindowCountDownLatch; |
| |
| @Override |
| public void onDetachedFromWindow() { |
| mOnDetachedFromWindowCalled = true; |
| mOnDetachedFromWindowCountDownLatch.countDown(); |
| } |
| |
| void setDetachedFromWindowLatch(CountDownLatch countDownLatch) { |
| mOnDetachedFromWindowCountDownLatch = countDownLatch; |
| } |
| } |
| } |