blob: f19ecffe8080175d53b9ca5764bdf8a790999396 [file] [log] [blame]
/*
* Copyright (C) 2021 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 android.server.wm.jetpack.utils;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
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.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.Application;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.window.extensions.layout.FoldingFeature;
import androidx.window.sidecar.SidecarDeviceState;
import org.junit.After;
import org.junit.Before;
import java.util.HashSet;
import java.util.Set;
/** Base class for all tests in the module. */
public class WindowManagerJetpackTestBase {
public static final String ACTIVITY_ID_LABEL = "ActivityID";
public Instrumentation mInstrumentation;
public Context mContext;
public Application mApplication;
private static final Set<Activity> sResumedActivities = new HashSet<>();
private static final Set<Activity> sVisibleActivities = new HashSet<>();
@Before
public void setUp() {
mInstrumentation = getInstrumentation();
assertNotNull(mInstrumentation);
mContext = getApplicationContext();
assertNotNull(mContext);
mApplication = (Application) mContext.getApplicationContext();
assertNotNull(mApplication);
// Register activity lifecycle callbacks to know which activities are resumed
registerActivityLifecycleCallbacks();
}
@After
public void tearDown() {
sResumedActivities.clear();
sVisibleActivities.clear();
}
public Activity startActivityNewTask(@NonNull Class activityClass) {
return startActivityNewTask(activityClass, null /* activityId */);
}
public Activity startActivityNewTask(@NonNull Class activityClass,
@Nullable String activityId) {
final Intent intent = new Intent(mContext, activityClass);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
if (activityId != null) {
intent.putExtra(ACTIVITY_ID_LABEL, activityId);
}
final Activity activity = mInstrumentation.startActivitySync(intent);
return activity;
}
/**
* Start an activity using a component name. Can be used for activities from a different UIDs.
*/
public void startActivityNewTask(@NonNull ComponentName activityComponent) {
final Intent intent = new Intent();
intent.setClassName(activityComponent.getPackageName(), activityComponent.getClassName());
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
/**
* Starts an instance of {@param activityToLaunchClass} from {@param activityToLaunchFrom}
* and returns the activity ID from the newly launched class.
*/
public static <T extends Activity> void startActivityFromActivity(Activity activityToLaunchFrom,
Class<T> activityToLaunchClass, String newActivityId) {
Intent intent = new Intent(activityToLaunchFrom, activityToLaunchClass);
intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
activityToLaunchFrom.startActivity(intent);
}
/**
* Starts a specified activity class from {@param activityToLaunchFrom}.
*/
public static void startActivityFromActivity(@NonNull Activity activityToLaunchFrom,
@NonNull ComponentName activityToLaunchComponent, @NonNull String newActivityId) {
Intent intent = new Intent();
intent.setClassName(activityToLaunchComponent.getPackageName(),
activityToLaunchComponent.getClassName());
intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
activityToLaunchFrom.startActivity(intent);
}
public static IBinder getActivityWindowToken(Activity activity) {
return activity.getWindow().getAttributes().token;
}
public static void assertHasNonNegativeDimensions(@NonNull Rect rect) {
assertFalse(rect.width() < 0 || rect.height() < 0);
}
public static void assertNotBothDimensionsZero(@NonNull Rect rect) {
assertFalse(rect.width() == 0 && rect.height() == 0);
}
public static Rect getActivityBounds(Activity activity) {
return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
}
public static Rect getMaximumActivityBounds(Activity activity) {
return activity.getWindowManager().getMaximumWindowMetrics().getBounds();
}
/**
* Gets the width of a full-screen task.
*/
public int getTaskWidth() {
return mContext.getSystemService(WindowManager.class).getMaximumWindowMetrics().getBounds()
.width();
}
public static void setActivityOrientationActivityHandlesOrientationChanges(
TestActivity activity, int orientation) {
// Make sure that the provided orientation is a fixed orientation
assertTrue(orientation == ORIENTATION_PORTRAIT || orientation == ORIENTATION_LANDSCAPE);
// Do nothing if the orientation already matches
if (activity.getResources().getConfiguration().orientation == orientation) {
return;
}
activity.resetLayoutCounter();
// Change the orientation
activity.setRequestedOrientation(orientation == ORIENTATION_PORTRAIT
? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
// Wait for the activity to layout, which will happen after the orientation change
assertTrue(activity.waitForLayout());
// Check that orientation matches
assertEquals(orientation, activity.getResources().getConfiguration().orientation);
}
public static void setActivityOrientationActivityDoesNotHandleOrientationChanges(
TestActivity activity, int orientation) {
// Make sure that the provided orientation is a fixed orientation
assertTrue(orientation == ORIENTATION_PORTRAIT || orientation == ORIENTATION_LANDSCAPE);
// Do nothing if the orientation already matches
if (activity.getResources().getConfiguration().orientation == orientation) {
return;
}
TestActivity.resetResumeCounter();
// Change the orientation
activity.setRequestedOrientation(orientation == ORIENTATION_PORTRAIT
? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
// The activity will relaunch because it does not handle the orientation change, so wait
// for the activity to be resumed again
assertTrue(activity.waitForOnResume());
// Check that orientation matches
assertEquals(orientation, activity.getResources().getConfiguration().orientation);
}
/**
* Returns whether the display rotates to respect activity orientation, which will be false if
* both portrait activities and landscape activities have the same maximum bounds. If the
* display rotates for orientation, then the maximum portrait bounds will be a rotated version
* of the maximum landscape bounds.
*/
public static boolean doesDisplayRotateForOrientation(@NonNull Rect portraitMaximumBounds,
@NonNull Rect landscapeMaximumBounds) {
return !portraitMaximumBounds.equals(landscapeMaximumBounds);
}
public static boolean areExtensionAndSidecarDeviceStateEqual(int extensionDeviceState,
int sidecarDeviceStatePosture) {
return (extensionDeviceState == FoldingFeature.STATE_FLAT
&& sidecarDeviceStatePosture == SidecarDeviceState.POSTURE_OPENED)
|| (extensionDeviceState == FoldingFeature.STATE_HALF_OPENED
&& sidecarDeviceStatePosture == SidecarDeviceState.POSTURE_HALF_OPENED);
}
private void registerActivityLifecycleCallbacks() {
mApplication.registerActivityLifecycleCallbacks(
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
synchronized (sVisibleActivities) {
sVisibleActivities.add(activity);
}
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
synchronized (sResumedActivities) {
sResumedActivities.add(activity);
}
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
synchronized (sResumedActivities) {
sResumedActivities.remove(activity);
}
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
synchronized (sVisibleActivities) {
sVisibleActivities.remove(activity);
}
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity,
@NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
});
}
public static boolean isActivityResumed(Activity activity) {
synchronized (sResumedActivities) {
return sResumedActivities.contains(activity);
}
}
public static boolean isActivityVisible(Activity activity) {
synchronized (sVisibleActivities) {
return sVisibleActivities.contains(activity);
}
}
@Nullable
public static TestActivityWithId getResumedActivityById(@NonNull String activityId) {
synchronized (sResumedActivities) {
for (Activity activity : sResumedActivities) {
if (activity instanceof TestActivityWithId
&& activityId.equals(((TestActivityWithId) activity).getId())) {
return (TestActivityWithId) activity;
}
}
return null;
}
}
@Nullable
public static Activity getTopResumedActivity() {
synchronized (sResumedActivities) {
return !sResumedActivities.isEmpty() ? sResumedActivities.iterator().next() : null;
}
}
}