diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:05:09 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:05:09 +0000 |
commit | dade553a08c3c517dd351ea1fcc46fde28c42713 (patch) | |
tree | bf324e7fbedbda684aa01ba62235e489d9a5f30c | |
parent | fefe95e52b2eab7a647df6a4be23244fc7b48cb5 (diff) | |
parent | 80987adf91866187c48e2a5d684dd3bbbea76109 (diff) | |
download | services-dade553a08c3c517dd351ea1fcc46fde28c42713.tar.gz |
Snap for 8564071 from 80987adf91866187c48e2a5d684dd3bbbea76109 to mainline-permission-releaseaml_per_331913010aml_per_331812030aml_per_331710050aml_per_331611010aml_per_331512020aml_per_331411000aml_per_331313010aml_per_331115020aml_per_331019040aml_per_330912010aml_per_330811030android13-mainline-permission-release
Change-Id: If75fa100a4a369bcf461d99c77d731937f98b07a
56 files changed, 3722 insertions, 1171 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b61e3aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*~ +.project +.classpath +*.iml +gen/ +*.pyc +*.swp +__pycache__ +.idea +/bin/ diff --git a/Android.bp b/Android.bp deleted file mode 100644 index d26195e..0000000 --- a/Android.bp +++ /dev/null @@ -1,35 +0,0 @@ -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -java_library { - name: "car-frameworks-service", - installable: true, - libs: [ - "services", - "android.hardware.automotive.vehicle-V2.0-java", - "com.android.car.internal.common", - ], - required: ["libcar-framework-service-jni"], - srcs: [ - "src/**/*.java", - ], - static_libs: [ - "android.car.watchdoglib", - "com.android.car.internal.system", - "android.automotive.watchdog.internal-java", - ], -} - -cc_library_shared { - name: "libcar-framework-service-jni", - shared_libs: [ - "libandroid_runtime", - "libhidlbase", - "liblog", - "libnativehelper", - "libsuspend", - "libutils", - ], - srcs: ["src/jni/com_android_internal_car_CarServiceHelperService.cpp"], -} diff --git a/builtInServices/Android.bp b/builtInServices/Android.bp new file mode 100644 index 0000000..eeda2da --- /dev/null +++ b/builtInServices/Android.bp @@ -0,0 +1,43 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_sdk_library { + name: "car-frameworks-service", + libs: [ + "services", + "android.car", + "android.car.builtin", // Will remove once split is complete + "android.hardware.automotive.vehicle-V2.0-java", + ], + srcs: [ + "src/**/*.java", + ], + static_libs: [ + "android.car.watchdoglib", + "android.automotive.watchdog.internal-V1-java", + ], + api_lint: { + enabled: true, + }, + + min_sdk_version: "33", + apex_available: [ + "//apex_available:platform", + "com.android.car.framework" + ], + + unsafe_ignore_missing_latest_api: true, + + test: { + enabled: false, + }, + system: { + enabled: true, + sdk_version: "module_current", + }, + module_lib: { + enabled: true, + sdk_version: "module_current", + }, +} diff --git a/PREUPLOAD.cfg b/builtInServices/PREUPLOAD.cfg index 2811ea9..2811ea9 100644 --- a/PREUPLOAD.cfg +++ b/builtInServices/PREUPLOAD.cfg diff --git a/builtInServices/TEST_MAPPING b/builtInServices/TEST_MAPPING new file mode 100644 index 0000000..2832b34 --- /dev/null +++ b/builtInServices/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "auto-presubmit": [ + { + "name": "CarServiceCrashDumpTest" + }, + { + "name": "FrameworkOptCarServicesTest" + } + ] +} diff --git a/builtInServices/api/current.txt b/builtInServices/api/current.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/api/module-lib-current.txt b/builtInServices/api/module-lib-current.txt new file mode 100644 index 0000000..ddf15cf --- /dev/null +++ b/builtInServices/api/module-lib-current.txt @@ -0,0 +1,96 @@ +// Signature format: 2.0 +package com.android.internal.car { + + public interface CarServiceHelperInterface { + method @Nullable public android.os.UserHandle createUserEvenWhenDisallowed(@Nullable String, @NonNull String, int); + method @Nullable public java.io.File dumpServiceStacks(); + method public void setSafetyMode(boolean); + } + + public interface CarServiceHelperServiceUpdatable { + method public void dump(@NonNull java.io.PrintWriter, @Nullable String[]); + method public com.android.server.wm.CarLaunchParamsModifierUpdatable getCarLaunchParamsModifierUpdatable(); + method public void initBootUser(); + method public void onFactoryReset(@NonNull java.util.function.BiConsumer<java.lang.Integer,android.os.Bundle>); + method public void onStart(); + method public void onUserRemoved(@NonNull android.os.UserHandle); + method public void sendUserLifecycleEvent(int, @Nullable android.os.UserHandle, @NonNull android.os.UserHandle); + } + +} + +package com.android.server.wm { + + public final class ActivityOptionsWrapper { + method public com.android.server.wm.TaskDisplayAreaWrapper getLaunchTaskDisplayArea(); + method public android.app.ActivityOptions getOptions(); + } + + public final class ActivityRecordWrapper { + method public boolean allowingEmbedded(); + method public android.content.ComponentName getComponentName(); + method public com.android.server.wm.TaskDisplayAreaWrapper getDisplayArea(); + method public int getHandoverLaunchDisplayId(); + method public com.android.server.wm.TaskDisplayAreaWrapper getHandoverTaskDisplayArea(); + method public int getUserId(); + method public boolean isDisplayTrusted(); + method public boolean isNoDisplay(); + } + + public final class CalculateParams { + method public com.android.server.wm.ActivityRecordWrapper getActivity(); + method public com.android.server.wm.LaunchParamsWrapper getCurrentParams(); + method public com.android.server.wm.ActivityOptionsWrapper getOptions(); + method public com.android.server.wm.LaunchParamsWrapper getOutParams(); + method public int getPhase(); + method public com.android.server.wm.RequestWrapper getRequest(); + method public com.android.server.wm.ActivityRecordWrapper getSource(); + method public com.android.server.wm.TaskWrapper getTask(); + method public com.android.server.wm.WindowLayoutWrapper getWindowLayout(); + method public boolean supportsMultiDisplay(); + } + + public interface CarLaunchParamsModifierInterface { + method @Nullable public com.android.server.wm.TaskDisplayAreaWrapper findTaskDisplayArea(int, int); + method @Nullable public com.android.server.wm.TaskDisplayAreaWrapper getDefaultTaskDisplayAreaOnDisplay(int); + method @NonNull public java.util.List<com.android.server.wm.TaskDisplayAreaWrapper> getFallbackDisplayAreasForActivity(@NonNull com.android.server.wm.ActivityRecordWrapper, @Nullable com.android.server.wm.RequestWrapper); + } + + public interface CarLaunchParamsModifierUpdatable { + method public int calculate(com.android.server.wm.CalculateParams); + method public android.hardware.display.DisplayManager.DisplayListener getDisplayListener(); + method public void handleCurrentUserSwitching(int); + method public void handleUserStarting(int); + method public void handleUserStopped(int); + } + + public final class LaunchParamsWrapper { + method public android.graphics.Rect getBounds(); + method public com.android.server.wm.TaskDisplayAreaWrapper getPreferredTaskDisplayArea(); + method public int getWindowingMode(); + method public void setBounds(android.graphics.Rect); + method public void setPreferredTaskDisplayArea(com.android.server.wm.TaskDisplayAreaWrapper); + method public void setWindowingMode(int); + field public static int RESULT_CONTINUE; + field public static int RESULT_DONE; + field public static int RESULT_SKIP; + } + + public final class RequestWrapper { + } + + public final class TaskDisplayAreaWrapper { + method public android.view.Display getDisplay(); + } + + public final class TaskWrapper { + method public com.android.server.wm.TaskWrapper getRootTask(); + method public com.android.server.wm.TaskDisplayAreaWrapper getTaskDisplayArea(); + method public int getUserId(); + } + + public final class WindowLayoutWrapper { + } + +} + diff --git a/builtInServices/api/module-lib-removed.txt b/builtInServices/api/module-lib-removed.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/api/removed.txt b/builtInServices/api/removed.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/api/system-current.txt b/builtInServices/api/system-current.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/api/system-removed.txt b/builtInServices/api/system-removed.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/api/test-current.txt b/builtInServices/api/test-current.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/test-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/api/test-removed.txt b/builtInServices/api/test-removed.txt new file mode 100644 index 0000000..d802177 --- /dev/null +++ b/builtInServices/api/test-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/builtInServices/host_tests/Android.bp b/builtInServices/host_tests/Android.bp new file mode 100644 index 0000000..9d68572 --- /dev/null +++ b/builtInServices/host_tests/Android.bp @@ -0,0 +1,14 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_test_host { + name: "CarServiceCrashDumpTest", + srcs: ["src/**/CarServiceCrashDumpTest.java"], + libs: [ + "compatibility-host-util", + "junit", + "tradefed", + "truth-prebuilt", + ], +} diff --git a/builtInServices/host_tests/src/com/android/internal/car/test/CarServiceCrashDumpTest.java b/builtInServices/host_tests/src/com/android/internal/car/test/CarServiceCrashDumpTest.java new file mode 100644 index 0000000..bf19a3c --- /dev/null +++ b/builtInServices/host_tests/src/com/android/internal/car/test/CarServiceCrashDumpTest.java @@ -0,0 +1,237 @@ +/* + * 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 com.android.internal.car.test; + +import static com.google.common.truth.Truth.assertWithMessage; + +import com.android.compatibility.common.util.CommonTestUtils; +import com.android.compatibility.common.util.PollingCheck; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import static org.junit.Assume.assumeTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(DeviceJUnit4ClassRunner.class) +public final class CarServiceCrashDumpTest extends BaseHostJUnit4Test { + private static final int DEFAULT_TIMEOUT_SEC = 20; + private static final long POLL_TIMEOUT_MS = 20000; + private static final String BUILD_TYPE_PROPERTY = "ro.build.type"; + + // This must be in sync with WatchDog lib. + private static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList( + "android.hardware.audio@4.0::IDevicesFactory", + "android.hardware.audio@5.0::IDevicesFactory", + "android.hardware.audio@6.0::IDevicesFactory", + "android.hardware.audio@7.0::IDevicesFactory", + "android.hardware.biometrics.face@1.0::IBiometricsFace", + "android.hardware.biometrics.fingerprint@2.1::IBiometricsFingerprint", + "android.hardware.bluetooth@1.0::IBluetoothHci", + "android.hardware.camera.provider@2.4::ICameraProvider", + "android.hardware.gnss@1.0::IGnss", + "android.hardware.graphics.allocator@2.0::IAllocator", + "android.hardware.graphics.composer@2.1::IComposer", + "android.hardware.health@2.0::IHealth", + "android.hardware.light@2.0::ILight", + "android.hardware.media.c2@1.0::IComponentStore", + "android.hardware.media.omx@1.0::IOmx", + "android.hardware.media.omx@1.0::IOmxStore", + "android.hardware.neuralnetworks@1.0::IDevice", + "android.hardware.power.stats@1.0::IPowerStats", + "android.hardware.sensors@1.0::ISensors", + "android.hardware.sensors@2.0::ISensors", + "android.hardware.sensors@2.1::ISensors", + "android.hardware.vr@1.0::IVr", + "android.system.suspend@1.0::ISystemSuspend" + ); + + // Which native processes to dump into dropbox's stack traces, must be in sync with Watchdog + // lib. + private static final String[] NATIVE_STACKS_OF_INTEREST = new String[] { + "/system/bin/audioserver", + "/system/bin/cameraserver", + "/system/bin/drmserver", + "/system/bin/keystore2", + "/system/bin/mediadrmserver", + "/system/bin/mediaserver", + "/system/bin/netd", + "/system/bin/sdcard", + "/system/bin/surfaceflinger", + "/system/bin/vold", + "media.extractor", // system/bin/mediaextractor + "media.metrics", // system/bin/mediametrics + "media.codec", // vendor/bin/hw/android.hardware.media.omx@1.0-service + "media.swcodec", // /apex/com.android.media.swcodec/bin/mediaswcodec + "media.transcoding", // Media transcoding service + "com.android.bluetooth", // Bluetooth service + "/apex/com.android.os.statsd/bin/statsd", // Stats daemon + }; + + /** + * Executes the shell command and returns the output. + */ + private String executeCommand(String command, Object... args) throws Exception { + String fullCommand = String.format(command, args); + return getDevice().executeShellCommand(fullCommand); + } + + /** + * Waits until the car service is ready. + */ + private void waitForCarServiceReady() throws Exception { + CommonTestUtils.waitUntil("timed out waiting for car service ", + DEFAULT_TIMEOUT_SEC, () -> isCarServiceReady()); + } + + private boolean isCarServiceReady() { + String cmd = "service check car_service"; + try { + String output = getDevice().executeShellCommand(cmd).strip(); + return !output.endsWith("not found"); + } catch (Exception e) { + CLog.w("%s failed: %s", cmd, e.getMessage()); + } + return false; + } + + @Before + public void setUp() throws Exception { + executeCommand("logcat -c"); + } + + /** + * Read the content of the dumped file. + */ + private String getDumpFile() throws Exception { + AtomicReference<String> log = new AtomicReference<>(); + String dumpString = "ActivityManager: Dumping to "; + String doneDumpingString = "ActivityManager: Done dumping"; + PollingCheck.check("dumpStackTrace not found in log", POLL_TIMEOUT_MS, () -> { + String logString = executeCommand("logcat -d"); + if (logString.contains("ActivityManager: dumpStackTraces") && logString.contains( + dumpString) && logString.contains(doneDumpingString)) { + log.set(logString); + return true; + } + return false; + }); + String logString = log.get(); + int start = logString.indexOf(dumpString) + dumpString.length(); + int end = logString.indexOf("\n", start); + if (end == -1) { + end = logString.length(); + } + return logString.substring(start, end); + } + + /** + * Get a list of PIDs for the interesting HALs that would be dumped. + */ + private List<String> getHalPids() throws Exception { + String lshalResult = executeCommand("lshal -i -p"); + List<String> pids = new ArrayList<String>(); + int i = 0; + for (String line: lshalResult.split("\n")) { + line = line.strip(); + if (line.equals("")) { + // When we see an empty line, we stops the parsing. + break; + } + if (i < 2) { + // Skip the first two lines + i++; + continue; + } + String[] fields = line.split("\\s+"); + for (String interestHal: HAL_INTERFACES_OF_INTEREST) { + if (fields[0].contains(interestHal)) { + pids.add(fields[1]); + break; + } + } + i++; + } + return pids; + } + + /** + * Get a list of PIDs for the native services that would be dumped. + */ + private List<String> getNativePids() throws Exception { + List<String> pids = new ArrayList<String>(); + for (String name: NATIVE_STACKS_OF_INTEREST) { + String pid = executeCommand(String.format("pidof %s", name)).strip(); + if (!pid.equals("")) { + pids.add(pid); + } + } + return pids; + } + + @Test + public void testCarServiceCrashDump() throws Exception { + String buildType = getDevice().getProperty("ro.build.type"); + // Only run on userdebug devices. + assumeTrue(buildType.equals("userdebug") || buildType.equals("eng")); + + List<String> pids = new ArrayList<String>(); + + getDevice().enableAdbRoot(); + + String systemServerPid = executeCommand(String.format("pidof %s", "system_server")).strip(); + assertWithMessage("system_service pid not empty").that(systemServerPid).isNotEmpty(); + pids.add(systemServerPid); + + List<String> halPids = getHalPids(); + pids.addAll(halPids); + assertWithMessage("hal pids").that(halPids.size() > 0).isTrue(); + + List<String> nativePids = getNativePids(); + pids.addAll(nativePids); + assertWithMessage("native pids").that(nativePids.size() > 0).isTrue(); + + executeCommand("am crash --user 0 com.android.car"); + + String dumpFile = getDumpFile(); + assertWithMessage("dump file").that(dumpFile).isNotEmpty(); + + String grepResult = executeCommand("cat %s", dumpFile); + + assertWithMessage("dumped content not empty").that(grepResult) + .isNotEmpty(); + + for (String pid : pids) { + assertWithMessage("dumped content contains interesting pid").that(grepResult) + .contains(String.format("----- pid %s at", pid)); + } + } + + @After + public void tearDown() throws Exception { + waitForCarServiceReady(); + } +} diff --git a/src/com/android/internal/car/CarDevicePolicySafetyChecker.java b/builtInServices/src/com/android/internal/car/CarDevicePolicySafetyChecker.java index 0bc0c21..2ae2e63 100644 --- a/src/com/android/internal/car/CarDevicePolicySafetyChecker.java +++ b/builtInServices/src/com/android/internal/car/CarDevicePolicySafetyChecker.java @@ -35,12 +35,12 @@ import android.annotation.NonNull; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.DevicePolicySafetyChecker; -import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,6 +48,8 @@ import java.util.stream.Collectors; /** * Integrates {@link android.app.admin.DevicePolicyManager} operations with car UX restrictions. + * + * @hide */ final class CarDevicePolicySafetyChecker { @@ -123,7 +125,7 @@ final class CarDevicePolicySafetyChecker { return mSafe.get(); } - void dump(@NonNull IndentingPrintWriter pw) { + void dump(@NonNull PrintWriter pw) { pw.printf("Safe to run device policy operations: %b\n", mSafe.get()); pw.printf("Unsafe operations: %s\n", Arrays.stream(UNSAFE_OPERATIONS) .mapToObj(o -> operationToString(o)).collect(Collectors.toList())); diff --git a/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java b/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java new file mode 100644 index 0000000..362fd07 --- /dev/null +++ b/builtInServices/src/com/android/internal/car/CarServiceHelperInterface.java @@ -0,0 +1,49 @@ +/* + * 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 com.android.internal.car; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.UserHandle; + +import java.io.File; + +/** + * Interface implemented by CarServiceHelperService. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public interface CarServiceHelperInterface { + + /** + * Sets safety mode + */ + void setSafetyMode(boolean safe); + + /** + * Creates user even when disallowed + */ + @Nullable + UserHandle createUserEvenWhenDisallowed(@Nullable String name, @NonNull String userType, + int flags); + + /** + * Dumps service stacks + */ + @Nullable + File dumpServiceStacks(); +} diff --git a/src/com/android/internal/car/CarServiceHelperService.java b/builtInServices/src/com/android/internal/car/CarServiceHelperService.java index 8e82316..4e2333c 100644 --- a/src/com/android/internal/car/CarServiceHelperService.java +++ b/builtInServices/src/com/android/internal/car/CarServiceHelperService.java @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.internal.car; -import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT; -import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE; +import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING; @@ -28,49 +26,36 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicySafetyChecker; import android.automotive.watchdog.internal.ICarWatchdogMonitor; -import android.automotive.watchdog.internal.PowerCycle; +import android.automotive.watchdog.internal.ProcessIdentifier; import android.automotive.watchdog.internal.StateType; +import android.car.builtin.util.EventLogHelper; import android.car.watchdoglib.CarWatchdogDaemonHelper; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; import android.content.pm.UserInfo; import android.hidl.manager.V1_0.IServiceManager; -import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.util.EventLog; -import android.util.IndentingPrintWriter; +import android.system.Os; +import android.system.OsConstants; +import android.util.Dumpable; import android.util.TimeUtils; -import com.android.car.internal.ICarServiceHelper; -import com.android.car.internal.ICarSystemServerClient; import com.android.car.internal.common.CommonConstants.UserLifecycleEventType; -import com.android.car.internal.common.EventLogTags; import com.android.car.internal.common.UserHelperLite; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; -import com.android.server.Dumpable; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -80,11 +65,13 @@ import com.android.server.pm.UserManagerInternal.UserLifecycleListener; import com.android.server.utils.Slogf; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.CarLaunchParamsModifier; +import com.android.server.wm.CarLaunchParamsModifierInterface; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.nio.file.Files; import java.nio.file.Path; @@ -93,24 +80,28 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * System service side companion service for CarService. Starts car service and provide necessary * API for CarService. Only for car product. + * + * @hide */ public class CarServiceHelperService extends SystemService - implements Dumpable, DevicePolicySafetyChecker { + implements Dumpable, DevicePolicySafetyChecker, CarServiceHelperInterface { - private static final String TAG = "CarServiceHelper"; + @VisibleForTesting + static final String TAG = "CarServiceHelper"; // TODO(b/154033860): STOPSHIP if they're still true private static final boolean DBG = true; private static final boolean VERBOSE = true; - private static final String PROP_RESTART_RUNTIME = "ro.car.recovery.restart_runtime.enabled"; - private static final List<String> CAR_HAL_INTERFACES_OF_INTEREST = Arrays.asList( "android.hardware.automotive.vehicle@2.0::IVehicle", "android.hardware.automotive.audiocontrol@1.0::IAudioControl", @@ -121,19 +112,16 @@ public class CarServiceHelperService extends SystemService private static final int WHAT_POST_PROCESS_DUMPING = 1; // Message ID representing process killing. private static final int WHAT_PROCESS_KILL = 2; - // Message ID representing service unresponsiveness. - private static final int WHAT_SERVICE_UNRESPONSIVE = 3; - - private static final long CAR_SERVICE_BINDER_CALL_TIMEOUT = 15_000; - private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0; + private static final String CSHS_UPDATABLE_CLASSNAME_STRING = + "com.android.internal.car.updatable.CarServiceHelperServiceUpdatableImpl"; + private static final String PROC_PID_STAT_PATTERN = + "(?<pid>[0-9]*)\\s\\((?<name>\\S+)\\)\\s\\S\\s(?:-?[0-9]*\\s){18}" + + "(?<startClockTicks>[0-9]*)\\s(?:-?[0-9]*\\s)*-?[0-9]*"; - private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl(); private final Context mContext; private final Object mLock = new Object(); @GuardedBy("mLock") - private IBinder mCarServiceBinder; - @GuardedBy("mLock") private boolean mSystemBootCompleted; private final CarLaunchParamsModifier mCarLaunchParamsModifier; @@ -142,14 +130,8 @@ public class CarServiceHelperService extends SystemService private final HandlerThread mHandlerThread = new HandlerThread("CarServiceHelperService"); private final ProcessTerminator mProcessTerminator = new ProcessTerminator(); - private final CarServiceConnectedCallback mCarServiceConnectedCallback = - new CarServiceConnectedCallback(); - private final CarServiceProxy mCarServiceProxy; - /** - * End-to-end time (from process start) for unlocking the first non-system user. - */ - private long mFirstUnlockedUserDuration; + private final Pattern mProcPidStatPattern = Pattern.compile(PROC_PID_STAT_PATTERN); private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper; private final ICarWatchdogMonitorImpl mCarWatchdogMonitor = new ICarWatchdogMonitorImpl(this); @@ -160,52 +142,21 @@ public class CarServiceHelperService extends SystemService } }; - private final ServiceConnection mCarServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - if (DBG) { - Slogf.d(TAG, "onServiceConnected: %s", iBinder); - } - handleCarServiceConnection(iBinder); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - handleCarServiceCrash(); - } - }; + private final CarDevicePolicySafetyChecker mCarDevicePolicySafetyChecker; - private final BroadcastReceiver mShutdownEventReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Skip immediately if intent is not relevant to device shutdown. - // FLAG_RECEIVER_FOREGROUND is checked to ignore the intent from UserController when - // a user is stopped. - if ((!intent.getAction().equals(Intent.ACTION_REBOOT) - && !intent.getAction().equals(Intent.ACTION_SHUTDOWN)) - || (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) == 0) { - return; - } - int powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER; - try { - mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE, - powerCycle, /* arg2= */ 0); - if (DBG) { - Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle); - } - } catch (RemoteException | RuntimeException e) { - Slogf.w(TAG, "Notifying power cycle state change failed: %s", e); - } - } - }; + private CarServiceHelperServiceUpdatable mCarServiceHelperServiceUpdatable; - private final CarDevicePolicySafetyChecker mCarDevicePolicySafetyChecker; + /** + * End-to-end time (from process start) for unlocking the first non-system user. + */ + private long mFirstUnlockedUserDuration; public CarServiceHelperService(Context context) { this(context, new CarLaunchParamsModifier(context), new CarWatchdogDaemonHelper(TAG), - null + /* carServiceHelperServiceUpdatable= */ null, + /* carDevicePolicySafetyChecker= */ null ); } @@ -214,7 +165,8 @@ public class CarServiceHelperService extends SystemService Context context, CarLaunchParamsModifier carLaunchParamsModifier, CarWatchdogDaemonHelper carWatchdogDaemonHelper, - CarServiceProxy carServiceOperationManager) { + @Nullable CarServiceHelperServiceUpdatable carServiceHelperServiceUpdatable, + @Nullable CarDevicePolicySafetyChecker carDevicePolicySafetyChecker) { super(context); mContext = context; @@ -222,9 +174,31 @@ public class CarServiceHelperService extends SystemService mHandler = new Handler(mHandlerThread.getLooper()); mCarLaunchParamsModifier = carLaunchParamsModifier; mCarWatchdogDaemonHelper = carWatchdogDaemonHelper; - mCarServiceProxy = - carServiceOperationManager == null ? new CarServiceProxy(this) - : carServiceOperationManager; + try { + if (carServiceHelperServiceUpdatable == null) { + mCarServiceHelperServiceUpdatable = (CarServiceHelperServiceUpdatable) Class + .forName(CSHS_UPDATABLE_CLASSNAME_STRING) + .getConstructor(Context.class, CarServiceHelperInterface.class, + CarLaunchParamsModifierInterface.class) + .newInstance(mContext, this, + mCarLaunchParamsModifier.getBuiltinInterface()); + Slogf.d(TAG, "CarServiceHelperServiceUpdatable created via reflection."); + } else { + mCarServiceHelperServiceUpdatable = carServiceHelperServiceUpdatable; + } + } catch (Exception e) { + // TODO(b/190458000): Define recovery mechanism. + // can't create the CarServiceHelperServiceUpdatable object + // crash the process + Slogf.w(TAG, e, "*** CARHELPER KILLING SYSTEM PROCESS: " + + "Can't create CarServiceHelperServiceUpdatable."); + Slogf.w(TAG, "*** GOODBYE!"); + Process.killProcess(Process.myPid()); + System.exit(10); + } + mCarLaunchParamsModifier.setUpdatable( + mCarServiceHelperServiceUpdatable.getCarLaunchParamsModifierUpdatable()); + UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); if (umi != null) { umi.addUserLifecycleListener(new UserLifecycleListener() { @@ -235,17 +209,20 @@ public class CarServiceHelperService extends SystemService @Override public void onUserRemoved(UserInfo user) { if (DBG) Slogf.d(TAG, "onUserRemoved(): $s", user.toFullString()); - mCarServiceProxy.onUserRemoved(user); + mCarServiceHelperServiceUpdatable.onUserRemoved(user.getUserHandle()); } }); } else { Slogf.e(TAG, "UserManagerInternal not available - should only happen on unit tests"); } - mCarDevicePolicySafetyChecker = new CarDevicePolicySafetyChecker(this); + mCarDevicePolicySafetyChecker = carDevicePolicySafetyChecker == null + ? new CarDevicePolicySafetyChecker(this) + : carDevicePolicySafetyChecker; } + @Override public void onBootPhase(int phase) { - EventLog.writeEvent(EventLogTags.CAR_HELPER_BOOT_PHASE, phase); + EventLogHelper.writeCarHelperBootPhase(phase); if (DBG) Slogf.d(TAG, "onBootPhase: %d", phase); TimingsTraceAndSlog t = newTimingsTraceAndSlog(); @@ -271,40 +248,26 @@ public class CarServiceHelperService extends SystemService @Override public void onStart() { - EventLog.writeEvent(EventLogTags.CAR_HELPER_START); + EventLogHelper.writeCarHelperStart(); - IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT); - filter.addAction(Intent.ACTION_SHUTDOWN); - mContext.registerReceiverForAllUsers(mShutdownEventReceiver, filter, null, null); mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener); mCarWatchdogDaemonHelper.connect(); - Intent intent = new Intent(); - intent.setPackage("com.android.car"); - intent.setAction(CAR_SERVICE_INTERFACE); - if (!mContext.bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE, - mHandler, UserHandle.SYSTEM)) { - Slogf.wtf(TAG, "cannot start car service"); - } - loadNativeLibrary(); + mCarServiceHelperServiceUpdatable.onStart(); } @Override - public void dump(IndentingPrintWriter pw, String[] args) { + public void dump(PrintWriter pw, String[] args) { + // Usage: adb shell dumpsys system_server_dumper --name CarServiceHelper if (args == null || args.length == 0 || args[0].equals("-a")) { pw.printf("System boot completed: %b\n", mSystemBootCompleted); pw.print("First unlocked user duration: "); TimeUtils.formatDuration(mFirstUnlockedUserDuration, pw); pw.println(); pw.printf("Queued tasks: %d\n", mProcessTerminator.mQueuedTask); - mCarServiceProxy.dump(pw); + mCarServiceHelperServiceUpdatable.dump(pw, args); mCarDevicePolicySafetyChecker.dump(pw); return; } - if ("--user-metrics-only".equals(args[0])) { - mCarServiceProxy.dumpUserMetrics(pw); - return; - } - if ("--is-operation-safe".equals(args[0]) & args.length > 1) { String arg1 = args[1]; int operation = 0; @@ -323,6 +286,12 @@ public class CarServiceHelperService extends SystemService DevicePolicyManager.operationSafetyReasonToString(reason)); return; } + + if ("--user-metrics-only".equals(args[0]) || "--dump-service-stacks".equals(args[0])) { + mCarServiceHelperServiceUpdatable.dump(pw, args); + return; + } + pw.printf("Invalid args: %s\n", Arrays.toString(args)); } @@ -334,17 +303,19 @@ public class CarServiceHelperService extends SystemService @Override public void onUserUnlocking(@NonNull TargetUser user) { if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)) return; - EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKING, user.getUserIdentifier()); + EventLogHelper.writeCarHelperUserUnlocking(user.getUserIdentifier()); if (DBG) Slogf.d(TAG, "onUserUnlocking(%s)", user); - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, user); + mCarServiceHelperServiceUpdatable + .sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, + /* userFrom= */ null, user.getUserHandle()); } @Override public void onUserUnlocked(@NonNull TargetUser user) { if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)) return; int userId = user.getUserIdentifier(); - EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKED, userId); + EventLogHelper.writeCarHelperUserUnlocked(userId); if (DBG) Slogf.d(TAG, "onUserUnlocked(%s)", user); if (mFirstUnlockedUserDuration == 0 && !UserHelperLite.isHeadlessSystemUser(userId)) { @@ -353,25 +324,30 @@ public class CarServiceHelperService extends SystemService Slogf.i(TAG, "Time to unlock 1st user(%s): %s", user, TimeUtils.formatDuration(mFirstUnlockedUserDuration)); } - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, user); + mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, + /* userFrom= */ null, user.getUserHandle()); } @Override public void onUserStarting(@NonNull TargetUser user) { if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STARTING)) return; - EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STARTING, user.getUserIdentifier()); + EventLogHelper.writeCarHelperUserStarting(user.getUserIdentifier()); if (DBG) Slogf.d(TAG, "onUserStarting(%s)", user); - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, user); + mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, + /* userFrom= */ null, user.getUserHandle()); + int userId = user.getUserIdentifier(); + mCarLaunchParamsModifier.handleUserStarting(userId); } @Override public void onUserStopping(@NonNull TargetUser user) { if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPING)) return; - EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPING, user.getUserIdentifier()); + EventLogHelper.writeCarHelperUserStopping(user.getUserIdentifier()); if (DBG) Slogf.d(TAG, "onUserStopping(%s)", user); - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, user); + mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, + /* userFrom= */ null, user.getUserHandle()); int userId = user.getUserIdentifier(); mCarLaunchParamsModifier.handleUserStopped(userId); } @@ -379,26 +355,45 @@ public class CarServiceHelperService extends SystemService @Override public void onUserStopped(@NonNull TargetUser user) { if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) return; - EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPED, user.getUserIdentifier()); + EventLogHelper.writeCarHelperUserStopped(user.getUserIdentifier()); if (DBG) Slogf.d(TAG, "onUserStopped(%s)", user); - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, user); + mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, + /* userFrom= */ null, user.getUserHandle()); } @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { if (isPreCreated(to, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) return; - EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_SWITCHING, + EventLogHelper.writeCarHelperUserSwitching( from == null ? UserHandle.USER_NULL : from.getUserIdentifier(), to.getUserIdentifier()); if (DBG) Slogf.d(TAG, "onUserSwitching(%s>>%s)", from, to); - mCarServiceProxy.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, - from, to); + mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent( + USER_LIFECYCLE_EVENT_TYPE_SWITCHING, from.getUserHandle(), + to.getUserHandle()); int userId = to.getUserIdentifier(); mCarLaunchParamsModifier.handleCurrentUserSwitching(userId); } + @Override + public void onUserCompletedEvent(TargetUser user, UserCompletedEventType eventType) { + if (user.isPreCreated()) { + if (DBG) { + Slogf.d(TAG, "Ignoring USER_COMPLETED event %s for pre-created user %s", + eventType, user); + } + return; + } + + UserHandle handle = user.getUserHandle(); + if (eventType.includesOnUserUnlocked()) { + mCarServiceHelperServiceUpdatable.sendUserLifecycleEvent( + USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, /* userFrom= */ null, handle); + } + } + @Override // from DevicePolicySafetyChecker @OperationSafetyReason public int getUnsafeOperationReason(@DevicePolicyOperation int operation) { @@ -415,13 +410,16 @@ public class CarServiceHelperService extends SystemService @Override // from DevicePolicySafetyChecker public void onFactoryReset(IResultReceiver callback) { if (DBG) Slogf.d(TAG, "onFactoryReset: %s", callback); - - mCarServiceProxy.onFactoryReset(callback); - } - - @VisibleForTesting - void loadNativeLibrary() { - System.loadLibrary("car-framework-service-jni"); + if (callback != null) { + mCarServiceHelperServiceUpdatable.onFactoryReset((resultCode, resultData) -> { + try { + callback.send(resultCode, resultData); + } catch (RemoteException e) { + Slogf.w(TAG, e, + "Callback to DevicePolicySafetyChecker threw RemoteException"); + } + }); + } } private boolean isPreCreated(@NonNull TargetUser user, @UserLifecycleEventType int eventType) { @@ -433,25 +431,6 @@ public class CarServiceHelperService extends SystemService return true; } - @VisibleForTesting - void handleCarServiceConnection(IBinder iBinder) { - synchronized (mLock) { - if (mCarServiceBinder == iBinder) { - return; // already connected. - } - Slogf.i(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, iBinder); - mCarServiceBinder = iBinder; - Slogf.i(TAG, "**CarService connected**"); - } - - sendSetSystemServerConnectionsCall(); - - mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE); - mHandler.sendMessageDelayed( - obtainMessage(CarServiceHelperService::handleCarServiceUnresponsive, this) - .setWhat(WHAT_SERVICE_UNRESPONSIVE), CAR_SERVICE_BINDER_CALL_TIMEOUT); - } - private TimingsTraceAndSlog newTimingsTraceAndSlog() { return new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER); } @@ -459,50 +438,10 @@ public class CarServiceHelperService extends SystemService private void setupAndStartUsers(@NonNull TimingsTraceAndSlog t) { // TODO(b/156263735): decide if it should return in case the device's on Retail Mode t.traceBegin("setupAndStartUsers"); - mCarServiceProxy.initBootUser(); + mCarServiceHelperServiceUpdatable.initBootUser(); t.traceEnd(); } - private void handleCarServiceUnresponsive() { - // This should not happen. Calling this method means ICarSystemServerClient binder is not - // returned after service connection. and CarService has not connected in the given time. - Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService unresponsive."); - Slogf.w(TAG, "*** GOODBYE!"); - Process.killProcess(Process.myPid()); - System.exit(10); - } - - private void sendSetSystemServerConnectionsCall() { - Parcel data = Parcel.obtain(); - data.writeInterfaceToken(CAR_SERVICE_INTERFACE); - data.writeStrongBinder(mHelper.asBinder()); - data.writeStrongBinder(mCarServiceConnectedCallback.asBinder()); - IBinder binder; - synchronized (mLock) { - binder = mCarServiceBinder; - } - int code = IBinder.FIRST_CALL_TRANSACTION; - try { - if (VERBOSE) Slogf.v(TAG, "calling one-way binder transaction with code %d", code); - // oneway void setSystemServerConnections(in IBinder helper, in IBinder receiver) = 0; - binder.transact(code, data, null, Binder.FLAG_ONEWAY); - if (VERBOSE) Slogf.v(TAG, "finished one-way binder transaction with code %d", code); - } catch (RemoteException e) { - Slogf.w(TAG, "RemoteException from car service", e); - handleCarServiceCrash(); - } catch (RuntimeException e) { - Slogf.wtf(TAG, e, "Exception calling binder transaction (real code: %d)", code); - throw e; - } finally { - data.recycle(); - } - } - - private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, - @NonNull TargetUser user) { - mCarServiceProxy.sendUserLifecycleEvent(eventType, /* from= */ null, user); - } - // Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java // TODO(b/131861630) use implementation common with Watchdog.java // @@ -546,60 +485,55 @@ public class CarServiceHelperService extends SystemService return pids; } + /** + * Dumps service stack + */ // Borrowed from Watchdog.java. Create an ANR file from the call stacks. - // - private static void dumpServiceStacks() { + @Override + @Nullable + public File dumpServiceStacks() { ArrayList<Integer> pids = new ArrayList<>(); pids.add(Process.myPid()); - ActivityManagerService.dumpStackTraces( + return ActivityManagerService.dumpStackTraces( pids, null, null, getInterestingNativePids(), null); } - @VisibleForTesting - void handleCarServiceCrash() { - // Recovery behavior. Kill the system server and reset - // everything if enabled by the property. - boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false); - - mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE); - - dumpServiceStacks(); - if (restartOnServiceCrash) { - Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService crash"); - Slogf.w(TAG, "*** GOODBYE!"); - Process.killProcess(Process.myPid()); - System.exit(10); - } else { - Slogf.w(TAG, "*** CARHELPER ignoring: CarService crash"); - } - } - - private void handleClientsNotResponding(@NonNull int[] pids) { - mProcessTerminator.requestTerminateProcess(pids); + private void handleClientsNotResponding(@NonNull List<ProcessIdentifier> processIdentifiers) { + mProcessTerminator.requestTerminateProcess(processIdentifiers); } private void registerMonitorToWatchdogDaemon() { try { mCarWatchdogDaemonHelper.registerMonitor(mCarWatchdogMonitor); + synchronized (mLock) { + if (!mSystemBootCompleted) { + return; + } + } + mCarWatchdogDaemonHelper.notifySystemStateChange( + StateType.BOOT_PHASE, SystemService.PHASE_BOOT_COMPLETED, /* arg2= */ 0); } catch (RemoteException | RuntimeException e) { Slogf.w(TAG, "Cannot register to car watchdog daemon: %s", e); } } - private void killProcessAndReportToMonitor(int pid) { - String processName = getProcessName(pid); - Process.killProcess(pid); - Slogf.w(TAG, "carwatchdog killed %s (pid: %d)", processName, pid); + private void killProcessAndReportToMonitor(ProcessIdentifier processIdentifier) { + ProcessInfo processInfo = getProcessInfo(processIdentifier.pid); + if (!processInfo.doMatch(processIdentifier.pid, processIdentifier.startTimeMillis)) { + return; + } + String cmdline = getProcessCmdLine(processIdentifier.pid); + Process.killProcess(processIdentifier.pid); + Slogf.w(TAG, "carwatchdog killed %s %s", cmdline, processInfo); try { - mCarWatchdogDaemonHelper.tellDumpFinished(mCarWatchdogMonitor, pid); + mCarWatchdogDaemonHelper.tellDumpFinished(mCarWatchdogMonitor, processIdentifier); } catch (RemoteException | RuntimeException e) { Slogf.w(TAG, "Cannot report monitor result to car watchdog daemon: %s", e); } } - private static String getProcessName(int pid) { - String unknownProcessName = "unknown process"; + private static String getProcessCmdLine(int pid) { String filename = "/proc/" + pid + "/cmdline"; try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { String line = reader.readLine().replace('\0', ' ').trim(); @@ -608,74 +542,53 @@ public class CarServiceHelperService extends SystemService line = line.substring(0, index); } return Paths.get(line).getFileName().toString(); - } catch (IOException e) { + } catch (IOException | RuntimeException e) { Slogf.w(TAG, "Cannot read %s", filename); - return unknownProcessName; + return ProcessInfo.UNKNOWN_PROCESS; } } - private static native int nativeForceSuspend(int timeoutMs); - - // TODO(b/173664653): it's missing unit tests (for example, to make sure that - // when its setSafetyMode() is called, mCarDevicePolicySafetyChecker is updated). - private class ICarServiceHelperImpl extends ICarServiceHelper.Stub { - /** - * Force device to suspend - */ - @Override // Binder call - public int forceSuspend(int timeoutMs) { - int retVal; - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - final long ident = Binder.clearCallingIdentity(); - try { - retVal = nativeForceSuspend(timeoutMs); - } finally { - Binder.restoreCallingIdentity(ident); + private ProcessInfo getProcessInfo(int pid) { + String filename = "/proc/" + pid + "/stat"; + try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { + String line = reader.readLine().replace('\0', ' ').trim(); + Matcher m = mProcPidStatPattern.matcher(line); + if (m.find()) { + int readPid = Integer.parseInt(Objects.requireNonNull(m.group("pid"))); + if (readPid == pid) { + return new ProcessInfo(pid, m.group("name"), + Long.parseLong(Objects.requireNonNull(m.group("startClockTicks")))); + } } - return retVal; - } - - @Override - public void setDisplayAllowlistForUser(@UserIdInt int userId, int[] displayIds) { - mCarLaunchParamsModifier.setDisplayAllowListForUser(userId, displayIds); - } - - @Override - public void setPassengerDisplays(int[] displayIdsForPassenger) { - mCarLaunchParamsModifier.setPassengerDisplays(displayIdsForPassenger); + } catch (IOException | RuntimeException e) { + Slogf.w(TAG, e, "Cannot read %s", filename); } + return new ProcessInfo(pid, ProcessInfo.UNKNOWN_PROCESS, ProcessInfo.INVALID_START_TIME); + } - @Override - public void setSourcePreferredComponents(boolean enableSourcePreferred, - @Nullable List<ComponentName> sourcePreferredComponents) { - mCarLaunchParamsModifier.setSourcePreferredComponents( - enableSourcePreferred, sourcePreferredComponents); - } + @Override + public void setSafetyMode(boolean safe) { + mCarDevicePolicySafetyChecker.setSafe(safe); + } - @Override - public void setSafetyMode(boolean safe) { - mCarDevicePolicySafetyChecker.setSafe(safe); + @Override + public UserHandle createUserEvenWhenDisallowed(String name, String userType, int flags) { + if (DBG) { + Slogf.d(TAG, "createUserEvenWhenDisallowed(): name=%s, type=%s, flags=%s", + UserHelperLite.safeName(name), userType, UserInfo.flagsToString(flags)); } - - @Override - public UserInfo createUserEvenWhenDisallowed(String name, String userType, int flags) { + UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + try { + UserInfo user = umi.createUserEvenWhenDisallowed(name, userType, flags, + /* disallowedPackages= */ null, /* token= */ null); if (DBG) { - Slogf.d(TAG, "createUserEvenWhenDisallowed(): name=%s, type=%s, flags=%s", - UserHelperLite.safeName(name), userType, UserInfo.flagsToString(flags)); - } - UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); - try { - UserInfo user = umi.createUserEvenWhenDisallowed(name, userType, flags, - /* disallowedPackages= */ null, /* token= */ null); - if (DBG) { - Slogf.d(TAG, "User created: %s", (user == null ? "null" : user.toFullString())); - } - // TODO(b/172691310): decide if user should be affiliated when DeviceOwner is set - return user; - } catch (UserManager.CheckedUserOperationException e) { - Slogf.e(TAG, "Error creating user", e); - return null; + Slogf.d(TAG, "User created: %s", (user == null ? "null" : user.toFullString())); } + // TODO(b/172691310): decide if user should be affiliated when DeviceOwner is set + return user.getUserHandle(); + } catch (UserManager.CheckedUserOperationException e) { + Slogf.e(TAG, e, "Error creating user"); + return null; } } @@ -687,12 +600,22 @@ public class CarServiceHelperService extends SystemService } @Override - public void onClientsNotResponding(int[] pids) { + public void onClientsNotResponding(List<ProcessIdentifier> processIdentifiers) { CarServiceHelperService service = mService.get(); - if (service == null || pids == null || pids.length == 0) { + if (service == null || processIdentifiers == null || processIdentifiers.isEmpty()) { return; } - service.handleClientsNotResponding(pids); + service.handleClientsNotResponding(processIdentifiers); + } + + @Override + public String getInterfaceHash() { + return ICarWatchdogMonitor.HASH; + } + + @Override + public int getInterfaceVersion() { + return ICarWatchdogMonitor.VERSION; } } @@ -705,7 +628,7 @@ public class CarServiceHelperService extends SystemService @GuardedBy("mProcessLock") private int mQueuedTask; - public void requestTerminateProcess(@NonNull int[] pids) { + public void requestTerminateProcess(@NonNull List<ProcessIdentifier> processIdentifiers) { synchronized (mProcessLock) { // If there is a running thread, we re-use it instead of starting a new thread. if (mExecutor == null) { @@ -714,8 +637,13 @@ public class CarServiceHelperService extends SystemService mQueuedTask++; } mExecutor.execute(() -> { - for (int pid : pids) { - dumpAndKillProcess(pid); + for (int i = 0; i < processIdentifiers.size(); i++) { + ProcessIdentifier processIdentifier = processIdentifiers.get(i); + ProcessInfo processInfo = getProcessInfo(processIdentifier.pid); + if (processInfo.doMatch(processIdentifier.pid, + processIdentifier.startTimeMillis)) { + dumpAndKillProcess(processIdentifier); + } } // mExecutor will be stopped from the main thread, if there is no queued task. mHandler.sendMessage(obtainMessage(ProcessTerminator::postProcessing, this) @@ -733,17 +661,17 @@ public class CarServiceHelperService extends SystemService } } - private void dumpAndKillProcess(int pid) { + private void dumpAndKillProcess(ProcessIdentifier processIdentifier) { if (DBG) { - Slogf.d(TAG, "Dumping and killing process(pid: %d)", pid); + Slogf.d(TAG, "Dumping and killing process(pid: %d)", processIdentifier.pid); } ArrayList<Integer> javaPids = new ArrayList<>(1); ArrayList<Integer> nativePids = new ArrayList<>(); try { - if (isJavaApp(pid)) { - javaPids.add(pid); + if (isJavaApp(processIdentifier.pid)) { + javaPids.add(processIdentifier.pid); } else { - nativePids.add(pid); + nativePids.add(processIdentifier.pid); } } catch (IOException e) { Slogf.w(TAG, "Cannot get process information: %s", e); @@ -760,10 +688,10 @@ public class CarServiceHelperService extends SystemService if (dumpTime < ONE_SECOND_MS) { mHandler.sendMessageDelayed(obtainMessage( CarServiceHelperService::killProcessAndReportToMonitor, - CarServiceHelperService.this, pid).setWhat(WHAT_PROCESS_KILL), + CarServiceHelperService.this, processIdentifier).setWhat(WHAT_PROCESS_KILL), ONE_SECOND_MS - dumpTime); } else { - killProcessAndReportToMonitor(pid); + killProcessAndReportToMonitor(processIdentifier); } } @@ -772,25 +700,44 @@ public class CarServiceHelperService extends SystemService String target = Files.readSymbolicLink(exePath).toString(); // Zygote's target exe is also /system/bin/app_process32 or /system/bin/app_process64. // But, we can be very sure that Zygote will not be the client of car watchdog daemon. - return target == "/system/bin/app_process32" || target == "/system/bin/app_process64"; + return target.equals("/system/bin/app_process32") || + target.equals("/system/bin/app_process64"); } } - private final class CarServiceConnectedCallback extends IResultReceiver.Stub { - @Override - public void send(int resultCode, Bundle resultData) { - mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE); - - IBinder binder; - if (resultData == null - || (binder = resultData.getBinder(ICAR_SYSTEM_SERVER_CLIENT)) == null) { - Slogf.wtf(TAG, "setSystemServerConnections return NULL Binder."); - handleCarServiceUnresponsive(); - return; - } + private static final class ProcessInfo { + public static final String UNKNOWN_PROCESS = "unknown process"; + public static final int INVALID_START_TIME = -1; + + private static final long MILLIS_PER_JIFFY = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK); + + public final int pid; + public final String name; + public final long startTimeMillis; + + ProcessInfo(int pid, String name, long startClockTicks) { + this.pid = pid; + this.name = name; + this.startTimeMillis = startClockTicks != INVALID_START_TIME + ? startClockTicks * MILLIS_PER_JIFFY : INVALID_START_TIME; + } + + boolean doMatch(int pid, long startTimeMillis) { + // Start time reported by the services that monitor the process health will be either + // the actual start time of the pid or the elapsed real time when the pid was last seen + // alive. Thus, verify whether the given start time is at least the actual start time of + // the pid. + return this.pid == pid && (this.startTimeMillis == INVALID_START_TIME + || this.startTimeMillis <= startTimeMillis); + } - ICarSystemServerClient carService = ICarSystemServerClient.Stub.asInterface(binder); - mCarServiceProxy.handleCarServiceConnection(carService); + @Override + public String toString() { + return new StringBuilder("ProcessInfo { pid = ").append(pid) + .append(", name = ").append(name) + .append(", startTimeMillis = ") + .append(startTimeMillis != INVALID_START_TIME ? startTimeMillis : "invalid") + .append(" }").toString(); } } } diff --git a/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java b/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java new file mode 100644 index 0000000..175da48 --- /dev/null +++ b/builtInServices/src/com/android/internal/car/CarServiceHelperServiceUpdatable.java @@ -0,0 +1,52 @@ +/* + * 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 com.android.internal.car; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.UserHandle; + +import com.android.server.wm.CarLaunchParamsModifierUpdatable; + +import java.io.PrintWriter; +import java.util.function.BiConsumer; + +/** + * Contains calls from CarServiceHelperService (which is a built-in class) to + * CarServiceHelperServiceUpdatableImpl (which is a updatable class as part of car-module). + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public interface CarServiceHelperServiceUpdatable { + + void onUserRemoved(@NonNull UserHandle userHandle); + + void onStart(); + + void dump(@NonNull PrintWriter pw, @Nullable String[] args); + + void sendUserLifecycleEvent(int eventType, @Nullable UserHandle userFrom, + @NonNull UserHandle userTo); + + void onFactoryReset(@NonNull BiConsumer<Integer, Bundle> processFactoryReset); + + void initBootUser(); + + CarLaunchParamsModifierUpdatable getCarLaunchParamsModifierUpdatable(); +} diff --git a/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java b/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java new file mode 100644 index 0000000..f9dc751 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/ActivityOptionsWrapper.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 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 android.annotation.SystemApi; +import android.app.ActivityOptions; +import android.window.WindowContainerToken; + +/** + * Wrapper of {@link ActivityOptions}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class ActivityOptionsWrapper { + private final ActivityOptions mOptions; + + private ActivityOptionsWrapper(ActivityOptions options) { + mOptions = options; + } + + /** @hide */ + public static ActivityOptionsWrapper create(ActivityOptions options) { + if (options == null) return null; + return new ActivityOptionsWrapper(options); + } + + /** + * Gets the underlying {@link ActivityOptions} that is wrapped by this instance. + */ + // Exposed the original object in order to allow to use the public accessors. + public ActivityOptions getOptions() { + return mOptions; + } + + /** + * Gets {@link TaskDisplayAreaWrapper} to launch the Activity into + */ + public TaskDisplayAreaWrapper getLaunchTaskDisplayArea() { + WindowContainerToken daToken = mOptions.getLaunchTaskDisplayArea(); + if (daToken == null) return null; + TaskDisplayArea tda = (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()); + return TaskDisplayAreaWrapper.create(tda); + } + + @Override + public String toString() { + return mOptions.toString(); + } +} diff --git a/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java b/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java new file mode 100644 index 0000000..bd15a54 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/ActivityRecordWrapper.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 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 android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; + +/** + * Wrapper of {@link ActivityRecord}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class ActivityRecordWrapper { + private final ActivityRecord mActivityRecord; + + private ActivityRecordWrapper(ActivityRecord activityRecord) { + mActivityRecord = activityRecord; + } + + /** @hide */ + public static ActivityRecordWrapper create(@Nullable ActivityRecord activityRecord) { + if (activityRecord == null) return null; + return new ActivityRecordWrapper(activityRecord); + } + + /** @hide */ + public ActivityRecord getActivityRecord() { + return mActivityRecord; + } + + /** + * Gets which user this Activity is running for. + */ + public int getUserId() { + return mActivityRecord.mUserId; + } + + /** + * Gets the actual {@link ComponentName} of this Activity. + */ + public ComponentName getComponentName() { + if (mActivityRecord.info == null) return null; + return mActivityRecord.info.getComponentName(); + } + + /** + * Gets the {@link TaskDisplayAreaWrapper} where this is located. + */ + public TaskDisplayAreaWrapper getDisplayArea() { + return TaskDisplayAreaWrapper.create(mActivityRecord.getDisplayArea()); + } + + /** + * Returns whether this Activity is not displayed. + */ + public boolean isNoDisplay() { + return mActivityRecord.noDisplay; + } + + /** + * Gets {@link TaskDisplayAreaWrapper} where the handover Activity is supposed to launch + */ + public TaskDisplayAreaWrapper getHandoverTaskDisplayArea() { + return TaskDisplayAreaWrapper.create(mActivityRecord.mHandoverTaskDisplayArea); + } + + /** + * Gets {@code displayId} where the handover Activity is supposed to launch + */ + public int getHandoverLaunchDisplayId() { + return mActivityRecord.mHandoverLaunchDisplayId; + } + + /** + * Returns whether this Activity allows to be embedded in the other Activity. + */ + public boolean allowingEmbedded() { + if (mActivityRecord.info == null) return false; + return (mActivityRecord.info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; + } + + /** + * Returns whether the display where this Activity is located is trusted. + */ + public boolean isDisplayTrusted() { + return mActivityRecord.getDisplayContent().isTrusted(); + } + + @Override + public String toString() { + return mActivityRecord.toString(); + } +} diff --git a/builtInServices/src/com/android/server/wm/CalculateParams.java b/builtInServices/src/com/android/server/wm/CalculateParams.java new file mode 100644 index 0000000..8b4faff --- /dev/null +++ b/builtInServices/src/com/android/server/wm/CalculateParams.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2022 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 android.annotation.SystemApi; +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.view.WindowLayout; + +/** + * Wrapper of the parameters of {@code LaunchParamsController.LaunchParamsModifier.onCalculate()} + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class CalculateParams { + private TaskWrapper mTask; + private WindowLayoutWrapper mLayout; + private ActivityRecordWrapper mActivity; + private ActivityRecordWrapper mSource; + private ActivityOptionsWrapper mOptions; + private RequestWrapper mRequest; + private int mPhase; + private LaunchParamsWrapper mCurrentParams; + private LaunchParamsWrapper mOutParams; + private boolean mSupportsMultiDisplay; + + private CalculateParams() {} + + /** @hide */ + public static CalculateParams create(Task task, ActivityInfo.WindowLayout layout, + ActivityRecord actvity, ActivityRecord source, + ActivityOptions options, ActivityStarter.Request request, int phase, + LaunchParamsController.LaunchParams currentParams, + LaunchParamsController.LaunchParams outParms, + boolean supportsMultiDisplay) { + CalculateParams params = new CalculateParams(); + params.mTask = TaskWrapper.create(task); + params.mLayout = WindowLayoutWrapper.create(layout); + params.mActivity = ActivityRecordWrapper.create(actvity); + params.mSource = ActivityRecordWrapper.create(source); + params.mOptions = ActivityOptionsWrapper.create(options); + params.mRequest = RequestWrapper.create(request); + params.mPhase = phase; + params.mCurrentParams = LaunchParamsWrapper.create(currentParams); + params.mOutParams = LaunchParamsWrapper.create(outParms); + params.mSupportsMultiDisplay = supportsMultiDisplay; + return params; + } + + /** + * Gets the {@link TaskWrapper} currently being positioned. + */ + public TaskWrapper getTask() { + return mTask; + } + + /** + * Gets the specified {@link WindowLayoutWrapper}. + */ + public WindowLayoutWrapper getWindowLayout() { + return mLayout; + } + + /** + * Gets the {@link ActivityRecordWrapper} currently being positioned. + */ + public ActivityRecordWrapper getActivity() { + return mActivity; + } + + /** + * Gets the {@link ActivityRecordWrapper} from which activity was started from. + */ + public ActivityRecordWrapper getSource() { + return mSource; + } + + /** + * Gets the {@link ActivityOptionsWrapper} specified for the activity. + */ + public ActivityOptionsWrapper getOptions() { + return mOptions; + } + + /** + * Gets the optional {@link RequestWrapper} from the activity starter. + */ + public RequestWrapper getRequest() { + return mRequest; + } + + /** + * Gets the {@link LaunchParamsController.LaunchParamsModifier.Phase} that the resolution should + * finish. + */ + public int getPhase() { + return mPhase; + } + + /** + * Gets the current {@link LaunchParamsWrapper}. + */ + public LaunchParamsWrapper getCurrentParams() { + return mCurrentParams; + } + + /** + * Gets the resulting {@link LaunchParamsWrapper}. + */ + public LaunchParamsWrapper getOutParams() { + return mOutParams; + } + + /** + * Returns whether the current system supports the multiple display. + */ + public boolean supportsMultiDisplay() { + return mSupportsMultiDisplay; + } +} diff --git a/builtInServices/src/com/android/server/wm/CarDisplayAreaPolicyProvider.java b/builtInServices/src/com/android/server/wm/CarDisplayAreaPolicyProvider.java new file mode 100644 index 0000000..ea4a4bf --- /dev/null +++ b/builtInServices/src/com/android/server/wm/CarDisplayAreaPolicyProvider.java @@ -0,0 +1,140 @@ +/* + * 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 com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; +import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; +import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provider for platform-default car display area policy for reference design. + * + * @hide + */ +public class CarDisplayAreaPolicyProvider implements DisplayAreaPolicy.Provider { + + /** + * This display area is mandatory to be defined. This is where the applications will be + * launched. + */ + private static final int DEFAULT_APP_TASK_CONTAINER = FEATURE_DEFAULT_TASK_CONTAINER; + + /** + * The display partition to launch applications by default. This contains {@link + * #DEFAULT_APP_TASK_CONTAINER}. + */ + private static final int FOREGROUND_DISPLAY_AREA_ROOT = FEATURE_VENDOR_FIRST + 1; + + /** + * Background applications task container. + */ + private static final int BACKGROUND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2; + private static final int FEATURE_TASKDISPLAYAREA_PARENT = FEATURE_VENDOR_FIRST + 3; + + /** + * Control bar task container. + * + * Currently we are launching CarLauncher activity in this TDA. This is because the audio card + * implementation today is using fragments. If that changes in future then we can use the window + * instead to display that view instead of fragments that need an activity. + */ + private static final int CONTROL_BAR_DISPLAY_AREA = FEATURE_VENDOR_FIRST + 4; + + /** + * Feature to display the title bar. + */ + private static final int FEATURE_TITLE_BAR = FEATURE_VENDOR_FIRST + 5; + + /** + * Feature to display voice plate. + */ + private static final int FEATURE_VOICE_PLATE = FEATURE_VENDOR_FIRST + 6; + + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, + RootDisplayArea root, DisplayArea.Tokens imeContainer) { + + if (!content.isDefaultDisplay) { + return new DisplayAreaPolicy.DefaultProvider().instantiate(wmService, content, root, + imeContainer); + } + + TaskDisplayArea backgroundTaskDisplayArea = new TaskDisplayArea(content, wmService, + "BackgroundTaskDisplayArea", BACKGROUND_TASK_CONTAINER, + /* createdByOrganizer= */ false, /* canHostHomeTask= */ false); + + TaskDisplayArea controlBarDisplayArea = new TaskDisplayArea(content, wmService, + "ControlBarTaskDisplayArea", CONTROL_BAR_DISPLAY_AREA, + /* createdByOrganizer= */ false, /* canHostHomeTask= */ false); + + TaskDisplayArea voicePlateTaskDisplayArea = new TaskDisplayArea(content, wmService, + "VoicePlateTaskDisplayArea", FEATURE_VOICE_PLATE, + /* createdByOrganizer= */ false, /* canHostHomeTask= */ false); + + List<TaskDisplayArea> backgroundTdaList = new ArrayList<>(); + backgroundTdaList.add(voicePlateTaskDisplayArea); + backgroundTdaList.add(backgroundTaskDisplayArea); + backgroundTdaList.add(controlBarDisplayArea); + + // Root + DisplayAreaPolicyBuilder.HierarchyBuilder rootHierarchy = + new DisplayAreaPolicyBuilder.HierarchyBuilder(root) + .setTaskDisplayAreas(backgroundTdaList) + .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(wmService.mPolicy, + "ImePlaceholder", FEATURE_IME_PLACEHOLDER) + .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) + .build()) + // to make sure there are 2 children under root. + // TODO: replace when b/188102153 is resolved to set this to top. + .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(wmService.mPolicy, + "TaskDisplayAreaParent", FEATURE_TASKDISPLAYAREA_PARENT) + .and(TYPE_APPLICATION) + .build()); + + // Default application launches here + RootDisplayArea defaultAppsRoot = new DisplayAreaGroup(wmService, + "FeatureForegroundApplication", FOREGROUND_DISPLAY_AREA_ROOT); + TaskDisplayArea defaultAppTaskDisplayArea = new TaskDisplayArea(content, wmService, + "DefaultApplicationTaskDisplayArea", DEFAULT_APP_TASK_CONTAINER); + List<TaskDisplayArea> firstTdaList = new ArrayList<>(); + firstTdaList.add(defaultAppTaskDisplayArea); + DisplayAreaPolicyBuilder.HierarchyBuilder applicationHierarchy = + new DisplayAreaPolicyBuilder.HierarchyBuilder(defaultAppsRoot) + .setTaskDisplayAreas(firstTdaList) + .setImeContainer(imeContainer) + .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(wmService.mPolicy, + "ImePlaceholder", FEATURE_IME_PLACEHOLDER) + .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) + .build()) + .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder(wmService.mPolicy, + "TitleBar", FEATURE_TITLE_BAR) + .and(TYPE_APPLICATION_OVERLAY) + .build()); + + return new DisplayAreaPolicyBuilder() + .setRootHierarchy(rootHierarchy) + .addDisplayAreaGroupHierarchy(applicationHierarchy) + .build(wmService); + } +} diff --git a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifier.java b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifier.java new file mode 100644 index 0000000..6472ffb --- /dev/null +++ b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifier.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2019 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 com.android.server.wm.ActivityStarter.Request; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.view.Display; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class to control the assignment of a display for Car while launching a Activity. + * + * <p>This one controls which displays users are allowed to launch. + * The policy should be passed from car service through + * {@link com.android.internal.car.ICarServiceHelper} binder interfaces. If no policy is set, + * this module will not change anything for launch process.</p> + * + * <p> The policy can only affect which display passenger users can use. Current user, assumed + * to be a driver user, is allowed to launch any display always.</p> + * + * @hide + */ +public final class CarLaunchParamsModifier implements LaunchParamsController.LaunchParamsModifier { + + private final Context mContext; + + private DisplayManager mDisplayManager; // set only from init() + private ActivityTaskManagerService mAtm; // set only from init() + + private CarLaunchParamsModifierUpdatable mUpdatable; + + // getFallbackDisplayAreasForActivity() can return the most 3 {@link TaskDisplayAreaWrapper}. + private final ArrayList<TaskDisplayAreaWrapper> mFallBackDisplayAreaList = new ArrayList<>(3); + + /** Constructor. Can be constructed any time. */ + public CarLaunchParamsModifier(Context context) { + // This can be very early stage. So postpone interaction with other system until init. + mContext = context; + } + + public void setUpdatable(CarLaunchParamsModifierUpdatable updatable) { + mUpdatable = updatable; + } + + public CarLaunchParamsModifierInterface getBuiltinInterface() { + return mBuiltinInterface; + } + + /** + * Initializes all internal stuffs. This should be called only after ATMS, DisplayManagerService + * are ready. + */ + public void init() { + mAtm = (ActivityTaskManagerService) ActivityTaskManager.getService(); + LaunchParamsController controller = mAtm.mTaskSupervisor.getLaunchParamsController(); + controller.registerModifier(this); + mDisplayManager = mContext.getSystemService(DisplayManager.class); + mDisplayManager.registerDisplayListener(mUpdatable.getDisplayListener(), + new Handler(Looper.getMainLooper())); + } + + /** Notifies user switching. */ + public void handleUserStarting(@UserIdInt int startingUserId) { + mUpdatable.handleUserStarting(startingUserId); + } + + /** Notifies user switching. */ + public void handleCurrentUserSwitching(@UserIdInt int newUserId) { + mUpdatable.handleCurrentUserSwitching(newUserId); + } + + /** Notifies user stopped. */ + public void handleUserStopped(@UserIdInt int stoppedUser) { + mUpdatable.handleUserStopped(stoppedUser); + } + + /** + * Decides display to assign while an Activity is launched. + * + * <p>For current user (=driver), launching to any display is allowed as long as system + * allows it.</p> + * + * <p>For private display, do not change anything as private display has its own logic.</p> + * + * <p>For passenger displays, only run in allowed displays. If requested display is not + * allowed, change to the 1st allowed display.</p> + */ + @Override + @Result + public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout, + @Nullable ActivityRecord activity, @Nullable ActivityRecord source, + ActivityOptions options, @Nullable Request request, int phase, + LaunchParamsController.LaunchParams currentParams, + LaunchParamsController.LaunchParams outParams) { + CalculateParams params = CalculateParams.create(task, layout, activity, source, + options, request, phase, currentParams, outParams, mAtm.mSupportsMultiDisplay); + return mUpdatable.calculate(params); + } + + @Nullable + private TaskDisplayAreaWrapper getDefaultTaskDisplayAreaOnDisplay(int displayId) { + if (displayId == Display.INVALID_DISPLAY) { + return null; + } + DisplayContent dc = mAtm.mRootWindowContainer.getDisplayContentOrCreate(displayId); + if (dc == null) { + return null; + } + return TaskDisplayAreaWrapper.create(dc.getDefaultTaskDisplayArea()); + } + + /** + * Calculates the default {@link TaskDisplayAreaWrapper} for a task. We attempt to put + * the activity within the same display area if possible. The strategy is to find the display + * in the following order: + * + * <ol> + * <li>The display area of the top activity from the launching process will be used</li> + * <li>The display area of the top activity from the real launching process will be used + * </li> + * <li>Default display area from the associated root window container.</li> + * </ol> + * @param activityRecordWrapper the activity being started + * @param requestWrapper optional {@link RequestWrapper} made to start the activity record + * @return the list of {@link TaskDisplayAreaWrapper} to house the task + */ + private List<TaskDisplayAreaWrapper> getFallbackDisplayAreasForActivity( + @NonNull ActivityRecordWrapper activityRecordWrapper, + @Nullable RequestWrapper requestWrapper) { + ActivityRecord activityRecord = activityRecordWrapper.getActivityRecord(); + Request request = requestWrapper != null ? requestWrapper.getRequest() : null; + mFallBackDisplayAreaList.clear(); + + WindowProcessController controllerFromLaunchingRecord = mAtm.getProcessController( + activityRecord.launchedFromPid, activityRecord.launchedFromUid); + TaskDisplayArea displayAreaForLaunchingRecord = controllerFromLaunchingRecord == null + ? null : controllerFromLaunchingRecord.getTopActivityDisplayArea(); + if (displayAreaForLaunchingRecord != null) { + mFallBackDisplayAreaList.add( + TaskDisplayAreaWrapper.create(displayAreaForLaunchingRecord)); + } + + WindowProcessController controllerFromProcess = mAtm.getProcessController( + activityRecord.getProcessName(), activityRecord.getUid()); + TaskDisplayArea displayAreaForRecord = controllerFromProcess == null ? null + : controllerFromProcess.getTopActivityDisplayArea(); + if (displayAreaForRecord != null) { + mFallBackDisplayAreaList.add(TaskDisplayAreaWrapper.create(displayAreaForRecord)); + } + + WindowProcessController controllerFromRequest = + request == null ? null : mAtm.getProcessController(request.realCallingPid, + request.realCallingUid); + TaskDisplayArea displayAreaFromSourceProcess = controllerFromRequest == null ? null + : controllerFromRequest.getTopActivityDisplayArea(); + if (displayAreaFromSourceProcess != null) { + mFallBackDisplayAreaList.add( + TaskDisplayAreaWrapper.create(displayAreaFromSourceProcess)); + } + return mFallBackDisplayAreaList; + } + + @Nullable + private TaskDisplayAreaWrapper findTaskDisplayArea(int displayId, int featureId) { + DisplayContent display = mAtm.mRootWindowContainer.getDisplayContentOrCreate(displayId); + if (display == null) { + return null; + } + TaskDisplayArea tda = display.getItemFromTaskDisplayAreas( + displayArea -> displayArea.mFeatureId == featureId ? displayArea : null); + return TaskDisplayAreaWrapper.create(tda); + } + + private final CarLaunchParamsModifierInterface mBuiltinInterface + = new CarLaunchParamsModifierInterface() { + @Nullable + @Override + public TaskDisplayAreaWrapper findTaskDisplayArea(int displayId, int featureId) { + return CarLaunchParamsModifier.this.findTaskDisplayArea(displayId, featureId); + } + + @Nullable + @Override + public TaskDisplayAreaWrapper getDefaultTaskDisplayAreaOnDisplay(int displayId) { + return CarLaunchParamsModifier.this.getDefaultTaskDisplayAreaOnDisplay(displayId); + } + + @NonNull + @Override + public List<TaskDisplayAreaWrapper> getFallbackDisplayAreasForActivity( + @NonNull ActivityRecordWrapper activityRecord, @Nullable RequestWrapper request) { + return CarLaunchParamsModifier.this.getFallbackDisplayAreasForActivity( + activityRecord, request); + } + }; +} diff --git a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java new file mode 100644 index 0000000..8550c55 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierInterface.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.graphics.Rect; +import android.view.Display; + +import java.util.List; + +/** + * Interface implemented by {@code CarLaunchParamsModifier} and used by + * {@code CarLaunchParamsModifierUpdatable}. + * + * Because {@code CarLaunchParamsModifierUpdatable} calls {@code CarLaunchParamsModifierInterface} + * with {@code mLock} acquired, {@code CarLaunchParamsModifierInterface} shouldn't call + * {@code CarLaunchParamsModifierUpdatable} again during its execution. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public interface CarLaunchParamsModifierInterface { + /** + * Returns {@link TaskDisplayAreaWrapper} of the given {@code featureId} in the given + * {@code displayId}. + */ + @Nullable TaskDisplayAreaWrapper findTaskDisplayArea(int displayId, int featureId); + + /** + * Returns the default {@link TaskDisplayAreaWrapper} of the given {@code displayId}. + */ + @Nullable TaskDisplayAreaWrapper getDefaultTaskDisplayAreaOnDisplay(int displayId); + + /** + * Returns the list of fallback {@link TaskDisplayAreaWrapper} from the source of the request. + */ + @NonNull List<TaskDisplayAreaWrapper> getFallbackDisplayAreasForActivity( + @NonNull ActivityRecordWrapper activityRecord, @Nullable RequestWrapper request); + +} diff --git a/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java new file mode 100644 index 0000000..3abf54e --- /dev/null +++ b/builtInServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatable.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.UserIdInt; +import android.car.app.CarActivityManager; +import android.content.ComponentName; +import android.hardware.display.DisplayManager; +import android.os.ServiceSpecificException; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.SparseIntArray; +import android.view.Display; +import android.window.DisplayAreaOrganizer; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Updatable interface of CarLaunchParamsModifier. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public interface CarLaunchParamsModifierUpdatable { + + /** Returns {@link DisplayManager.DisplayListener} of CarLaunchParamsModifierUpdatable. */ + DisplayManager.DisplayListener getDisplayListener(); + + /** Notifies user switching. */ + void handleCurrentUserSwitching(@UserIdInt int newUserId); + + /** Notifies user starting. */ + void handleUserStarting(@UserIdInt int startingUser); + + /** Notifies user stopped. */ + void handleUserStopped(@UserIdInt int stoppedUser); + + /** + * Calculates {@code outParams} based on the given arguments. + * See {@code LaunchParamsController.LaunchParamsModifier.onCalculate()} for the detail. + */ + int calculate(CalculateParams params); +} diff --git a/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java b/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java new file mode 100644 index 0000000..344961f --- /dev/null +++ b/builtInServices/src/com/android/server/wm/LaunchParamsWrapper.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 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 android.annotation.Nullable; +import android.annotation.SystemApi; +import android.graphics.Rect; + +/** + * Wrapper of {@link com.android.server.wm.LaunchParamsController.LaunchParams}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class LaunchParamsWrapper { + /** Returned when the modifier does not want to influence the bounds calculation */ + public static int RESULT_SKIP = LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; + /** + * Returned when the modifier has changed the bounds and would like its results to be the + * final bounds applied. + */ + public static int RESULT_DONE = LaunchParamsController.LaunchParamsModifier.RESULT_DONE; + /** + * Returned when the modifier has changed the bounds but is okay with other modifiers + * influencing the bounds. + */ + public static int RESULT_CONTINUE = LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; + + private final LaunchParamsController.LaunchParams mLaunchParams; + + private LaunchParamsWrapper(LaunchParamsController.LaunchParams launchParams) { + mLaunchParams = launchParams; + } + + /** @hide */ + public static LaunchParamsWrapper create( + @Nullable LaunchParamsController.LaunchParams launchParams) { + if (launchParams == null) return null; + return new LaunchParamsWrapper(launchParams); + } + + /** + * Gets the {@link TaskDisplayAreaWrapper} the {@link Task} would prefer to be on. + */ + public TaskDisplayAreaWrapper getPreferredTaskDisplayArea() { + return TaskDisplayAreaWrapper.create(mLaunchParams.mPreferredTaskDisplayArea); + } + + /** + * Sets the {@link TaskDisplayAreaWrapper} the {@link Task} would prefer to be on. + */ + public void setPreferredTaskDisplayArea(TaskDisplayAreaWrapper tda) { + mLaunchParams.mPreferredTaskDisplayArea = tda.getTaskDisplayArea(); + } + + /** + * Gets the windowing mode to be in. + */ + public int getWindowingMode() { + return mLaunchParams.mWindowingMode; + } + + /** + * Sets the windowing mode to be in. + */ + public void setWindowingMode(int windowingMode) { + mLaunchParams.mWindowingMode = windowingMode; + } + + /** + * Gets the bounds within the parent container. + */ + public Rect getBounds() { + return mLaunchParams.mBounds; + } + + /** + * Sets the bounds within the parent container. + */ + public void setBounds(Rect bounds) { + mLaunchParams.mBounds.set(bounds); + } + + @Override + public String toString() { + return "LaunchParams{" + + "mPreferredTaskDisplayArea=" + mLaunchParams.mPreferredTaskDisplayArea + + ", mWindowingMode=" + mLaunchParams.mWindowingMode + + ", mBounds=" + mLaunchParams.mBounds.toString() + '}'; + } +} diff --git a/builtInServices/src/com/android/server/wm/RequestWrapper.java b/builtInServices/src/com/android/server/wm/RequestWrapper.java new file mode 100644 index 0000000..d6e1795 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/RequestWrapper.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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 android.annotation.Nullable; +import android.annotation.SystemApi; + +/** + * Wrapper of {@link com.android.server.wm.ActivityStarter.Request}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class RequestWrapper { + private final ActivityStarter.Request mRequest; + + private RequestWrapper(ActivityStarter.Request request) { + mRequest = request; + } + + /** @hide */ + public static RequestWrapper create(@Nullable ActivityStarter.Request request) { + if (request == null) return null; + return new RequestWrapper(request); + } + + /** @hide */ + public ActivityStarter.Request getRequest() { + return mRequest; + } + + @Override + public String toString() { + return mRequest.toString(); + } +} diff --git a/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java b/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java new file mode 100644 index 0000000..9faef06 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/TaskDisplayAreaWrapper.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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 android.annotation.Nullable; +import android.annotation.SystemApi; +import android.view.Display; + +/** + * Wrapper of {@link TaskDisplayArea}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TaskDisplayAreaWrapper { + private final TaskDisplayArea mTaskDisplayArea; + + private TaskDisplayAreaWrapper(TaskDisplayArea taskDisplayArea) { + mTaskDisplayArea = taskDisplayArea; + } + + /** @hide */ + public static TaskDisplayAreaWrapper create(@Nullable TaskDisplayArea taskDisplayArea) { + if (taskDisplayArea == null) return null; + return new TaskDisplayAreaWrapper(taskDisplayArea); + } + + /** @hide */ + public TaskDisplayArea getTaskDisplayArea() { + return mTaskDisplayArea; + } + + /** + * Gets the display this {@link TaskDisplayAreaWrapper} is on. + */ + public Display getDisplay() { + return mTaskDisplayArea.getDisplayContent().getDisplay(); + } + + @Override + public String toString() { + return mTaskDisplayArea.toString(); + } +} diff --git a/builtInServices/src/com/android/server/wm/TaskWrapper.java b/builtInServices/src/com/android/server/wm/TaskWrapper.java new file mode 100644 index 0000000..3a2c682 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/TaskWrapper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 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 android.annotation.Nullable; +import android.annotation.SystemApi; + +/** + * Wrapper of {@link Task}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TaskWrapper { + private final Task mTask; + + private TaskWrapper(Task task) { + mTask = task; + } + + /** @hide */ + public static TaskWrapper create(@Nullable Task task) { + if (task == null) return null; + return new TaskWrapper(task); + } + + /** + * Gets the {@code userId} of this {@link Task} is created for + */ + public int getUserId() { + return mTask.mUserId; + } + + /** + * Gets the root {@link TaskWrapper} of the this. + */ + public TaskWrapper getRootTask() { + return create(mTask.getRootTask()); + } + + /** + * Gets the {@link TaskDisplayAreaWrapper} this {@link Task} is on. + */ + public TaskDisplayAreaWrapper getTaskDisplayArea() { + return TaskDisplayAreaWrapper.create(mTask.getTaskDisplayArea()); + } + + @Override + public String toString() { + return mTask.toString(); + } +} diff --git a/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java b/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java new file mode 100644 index 0000000..e028a20 --- /dev/null +++ b/builtInServices/src/com/android/server/wm/WindowLayoutWrapper.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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 android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.pm.ActivityInfo; + +/** + * Wrapper of {@link android.content.pm.ActivityInfo.WindowLayout}. + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class WindowLayoutWrapper { + private final ActivityInfo.WindowLayout mLayout; + + private WindowLayoutWrapper(ActivityInfo.WindowLayout layout) { + mLayout = layout; + } + + /** @hide */ + public static WindowLayoutWrapper create(@Nullable ActivityInfo.WindowLayout layout) { + if (layout == null) return null; + return new WindowLayoutWrapper(layout); + } + + @Override + public String toString() { + return mLayout.toString(); + } +} diff --git a/tests/Android.mk b/builtInServices/tests/Android.mk index 1cf4dcc..c9eee58 100644 --- a/tests/Android.mk +++ b/builtInServices/tests/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) \ $(call all-java-files-under, ../src) \ $(call all-Iaidl-files-under, ../src) -LOCAL_PACKAGE_NAME := CarServicesTest +LOCAL_PACKAGE_NAME := FrameworkOptCarServicesTest LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 LOCAL_LICENSE_CONDITIONS := notice LOCAL_PRIVATE_PLATFORM_APIS := true @@ -23,21 +23,21 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) LOCAL_PROGUARD_ENABLED := disabled LOCAL_JAVA_LIBRARIES += \ + android.car \ android.test.runner \ android.test.base \ android.hardware.automotive.vehicle-V2.0-java \ - com.android.car.internal.common LOCAL_STATIC_JAVA_LIBRARIES := \ android.car.test.utils \ android.car.watchdoglib \ androidx.test.ext.junit \ androidx.test.rules \ - com.android.car.internal.system \ mockito-target-extended-minus-junit4 \ services.core \ testng \ - truth-prebuilt + truth-prebuilt \ + android.car.builtin \ # mockito-target-extended dependencies LOCAL_JNI_SHARED_LIBRARIES := \ diff --git a/tests/AndroidManifest.xml b/builtInServices/tests/AndroidManifest.xml index ff050df..edce0fd 100644 --- a/tests/AndroidManifest.xml +++ b/builtInServices/tests/AndroidManifest.xml @@ -22,7 +22,7 @@ android:targetPackage="com.android.internal.car" android:label="Unit Tests for Car Framework Services"/> - <application android:label="CarServicesTest" + <application android:label="FrameworkOptCarServicesTest" android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java b/builtInServices/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java index ef50f92..ef50f92 100644 --- a/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java +++ b/builtInServices/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java diff --git a/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java b/builtInServices/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java index a2da2a5..552a27a 100644 --- a/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java +++ b/builtInServices/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java @@ -16,23 +16,19 @@ package com.android.internal.car; -import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE; +import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.server.SystemService.UserCompletedEventType.newUserCompletedEventTypeForTest; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -40,18 +36,16 @@ import android.annotation.UserIdInt; import android.car.test.mocks.AbstractExtendedMockitoTestCase; import android.car.watchdoglib.CarWatchdogDaemonHelper; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Binder; import android.os.IBinder; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; - +import android.os.SystemProperties.Handle; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; +import com.android.server.SystemService.UserCompletedEventType; import com.android.server.wm.CarLaunchParamsModifier; import org.junit.Before; @@ -65,9 +59,6 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase { - private static final String TAG = CarServiceHelperServiceTest.class.getSimpleName(); - - private CarServiceHelperService mHelperSpy; private CarServiceHelperService mHelper; @Mock @@ -81,7 +72,14 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase @Mock private IBinder mICarBinder; @Mock - private CarServiceProxy mCarServiceProxy; + private CarServiceHelperServiceUpdatable mCarServiceHelperServiceUpdatable; + + @Mock + private CarDevicePolicySafetyChecker mCarDevicePolicySafetyChecker; + + public CarServiceHelperServiceTest() { + super(CarServiceHelperService.TAG); + } /** * Initialize objects and setup testing environment. @@ -97,40 +95,18 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase mMockContext, mCarLaunchParamsModifier, mCarWatchdogDaemonHelper, - mCarServiceProxy); - mHelperSpy = spy(mHelper); + mCarServiceHelperServiceUpdatable, + mCarDevicePolicySafetyChecker); when(mMockContext.getPackageManager()).thenReturn(mPackageManager); } @Test - public void testCarServiceLaunched() throws Exception { - mockRegisterReceiver(); - mockBindService(); - mockLoadLibrary(); - - mHelperSpy.onStart(); - - verifyBindService(); - } - - @Test - public void testHandleCarServiceCrash() throws Exception { - mockHandleCarServiceCrash(); - mockCarServiceException(); - - mHelperSpy.handleCarServiceConnection(mICarBinder); - - verify(mHelperSpy).handleCarServiceCrash(); - } - - @Test public void testOnUserStarting_notifiesICar() throws Exception { int userId = 10; mHelper.onUserStarting(newTargetUser(userId)); - verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STARTING, - UserHandle.USER_NULL, userId); + verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STARTING, userId); } @Test @@ -165,8 +141,7 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase mHelper.onUserUnlocking(newTargetUser(userId)); - verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, - UserHandle.USER_NULL, userId); + verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, userId); } @Test @@ -182,8 +157,7 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase mHelper.onUserStopping(newTargetUser(userId)); - verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPING, - UserHandle.USER_NULL, userId); + verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPING, userId); } @Test @@ -199,8 +173,7 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase mHelper.onUserStopped(newTargetUser(userId)); - verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPED, - UserHandle.USER_NULL, userId); + verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPED, userId); } @Test @@ -217,45 +190,39 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase verifyInitBootUser(); } - private TargetUser newTargetUser(int userId) { - return newTargetUser(userId, /* preCreated= */ false); - } + @Test + public void testOnUserCompletedEvent_notifiesPostUnlockedEvent() throws Exception { + int userId = 10; - private TargetUser newTargetUser(int userId, boolean preCreated) { - TargetUser targetUser = mock(TargetUser.class); - when(targetUser.getUserIdentifier()).thenReturn(userId); - when(targetUser.isPreCreated()).thenReturn(preCreated); - return targetUser; - } + mHelper.onUserCompletedEvent(newTargetUser(userId), newUserCompletedEventTypeForTest( + UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED)); - private void verifyBindService() throws Exception { - verify(mMockContext).bindServiceAsUser( - argThat(intent -> intent.getAction().equals(CAR_SERVICE_INTERFACE)), - any(), eq(Context.BIND_AUTO_CREATE), any(), eq(UserHandle.SYSTEM)); + verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_POST_UNLOCKED, userId); } - private void mockRegisterReceiver() { - when(mMockContext.registerReceiverForAllUsers(any(), any(), any(), any())) - .thenReturn(new Intent()); - } + @Test + public void testOnUserCompletedEvent_preCreatedUserDoesNotNotifyICar() throws Exception { + UserCompletedEventType userCompletedEventType = newUserCompletedEventTypeForTest( + UserCompletedEventType.EVENT_TYPE_USER_STARTING + | UserCompletedEventType.EVENT_TYPE_USER_SWITCHING + | UserCompletedEventType.EVENT_TYPE_USER_UNLOCKED); - private void mockBindService() { - when(mMockContext.bindServiceAsUser(any(), any(), - eq(Context.BIND_AUTO_CREATE), any(), eq(UserHandle.SYSTEM))) - .thenReturn(true); - } + mHelper.onUserCompletedEvent(newTargetUser(10, /* preCreated= */true), + userCompletedEventType); - private void mockLoadLibrary() { - doNothing().when(mHelperSpy).loadNativeLibrary(); + verifyICarOnUserLifecycleEventNeverCalled(); } - private void mockCarServiceException() throws Exception { - when(mICarBinder.transact(anyInt(), notNull(), isNull(), eq(Binder.FLAG_ONEWAY))) - .thenThrow(new RemoteException("mock car service Crash")); + private TargetUser newTargetUser(int userId) { + return newTargetUser(userId, /* preCreated= */ false); } - private void mockHandleCarServiceCrash() throws Exception { - doNothing().when(mHelperSpy).handleCarServiceCrash(); + private TargetUser newTargetUser(int userId, boolean preCreated) { + TargetUser targetUser = mock(TargetUser.class); + when(targetUser.getUserIdentifier()).thenReturn(userId); + when(targetUser.getUserHandle()).thenReturn(UserHandle.of(userId)); + when(targetUser.isPreCreated()).thenReturn(preCreated); + return targetUser; } enum InitialUserInfoAction { @@ -272,21 +239,22 @@ public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase private void verifyICarOnUserLifecycleEventCalled(int eventType, @UserIdInt int fromId, @UserIdInt int toId) throws Exception { - verify(mCarServiceProxy).sendUserLifecycleEvent(eq(eventType), - isTargetUser(fromId), isTargetUser(toId)); + verify(mCarServiceHelperServiceUpdatable).sendUserLifecycleEvent(eventType, + UserHandle.of(fromId), UserHandle.of(toId)); } - private static TargetUser isTargetUser(@UserIdInt int userId) { - return argThat((user) -> { - return user == null || user.getUserIdentifier() == userId; - }); + private void verifyICarOnUserLifecycleEventCalled(int eventType, + @UserIdInt int userId) throws Exception { + verify(mCarServiceHelperServiceUpdatable).sendUserLifecycleEvent(eventType, + null, UserHandle.of(userId)); } private void verifyICarOnUserLifecycleEventNeverCalled() throws Exception { - verify(mCarServiceProxy, never()).sendUserLifecycleEvent(anyInt(), any(), any()); + verify(mCarServiceHelperServiceUpdatable, never()).sendUserLifecycleEvent(anyInt(), any(), + any()); } private void verifyInitBootUser() throws Exception { - verify(mCarServiceProxy).initBootUser(); + verify(mCarServiceHelperServiceUpdatable).initBootUser(); } } diff --git a/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java new file mode 100644 index 0000000..682c31f --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/ActivityOptionsWrapperTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import android.app.ActivityOptions; + +import org.junit.Test; + +/** + * This class contains unit tests for the {@link ActivityOptionsWrapper}. + */ +public final class ActivityOptionsWrapperTest { + @Test + public void create_returnsActivityOptionWrapper() { + ActivityOptions options = ActivityOptions.makeBasic(); + ActivityOptionsWrapper wrapper = ActivityOptionsWrapper.create(options); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.getOptions()).isSameInstanceAs(options); + assertThat(wrapper.toString()).isEqualTo(options.toString()); + } + + @Test + public void create_returnsNull() { + ActivityOptionsWrapper wrapper = ActivityOptionsWrapper.create(null); + assertThat(wrapper).isNull(); + } +} diff --git a/builtInServices/tests/src/com/android/server/wm/ActivityRecordWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/ActivityRecordWrapperTest.java new file mode 100644 index 0000000..302f974 --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/ActivityRecordWrapperTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * This class contains unit tests for the {@link ActivityRecordWrapper}. + */ +@RunWith(MockitoJUnitRunner.class) +public final class ActivityRecordWrapperTest { + @Mock + private ActivityRecord mActivityRecord; + + @Test + public void create_returnsActivityOptionWrapper() { + ActivityRecordWrapper wrapper = ActivityRecordWrapper.create(mActivityRecord); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.getActivityRecord()).isSameInstanceAs(mActivityRecord); + assertThat(wrapper.toString()).isEqualTo(mActivityRecord.toString()); + } + + @Test + public void create_returnsNull() { + ActivityRecordWrapper wrapper = ActivityRecordWrapper.create(null); + assertThat(wrapper).isNull(); + } +} diff --git a/builtInServices/tests/src/com/android/server/wm/CalculateParamsTest.java b/builtInServices/tests/src/com/android/server/wm/CalculateParamsTest.java new file mode 100644 index 0000000..bf34497 --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/CalculateParamsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo; +import android.view.Gravity; + +import com.android.server.wm.LaunchParamsController.LaunchParams; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * This class contains unit tests for the {@link CalculateParams}. + */ +@RunWith(MockitoJUnitRunner.class) +public class CalculateParamsTest { + @Mock + private Task mTask; + private ActivityInfo.WindowLayout mLayout = new ActivityInfo.WindowLayout( + /* width= */ 1280, /* widthFraction= */ 0.5f, + /* height= */ 800, /* heightFraction= */ 1.0f, + /* gravity= */ Gravity.CENTER, /* minWidth= */ 400, /* minHeight= */ 300); + @Mock + private ActivityRecord mActvity; + @Mock + private ActivityRecord mSource; + private ActivityOptions mOptions = ActivityOptions.makeBasic(); + private ActivityStarter.Request mRequest = new ActivityStarter.Request(); + private int mPhase = LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; + private LaunchParams mCurrentParams = new LaunchParams(); + private LaunchParams mOutParms = new LaunchParams(); + boolean mSupportsMultiDisplay = true; + + @Test + public void createReturnsCalculateParams() { + mCurrentParams.mWindowingMode = WINDOWING_MODE_FULLSCREEN; + mOutParms.mWindowingMode = WINDOWING_MODE_MULTI_WINDOW; + CalculateParams params = CalculateParams.create(mTask,mLayout, mActvity, mSource, mOptions, + mRequest, mPhase, mCurrentParams, mOutParms, mSupportsMultiDisplay); + // Current toString() of Wrappers are using toString() of the underlying object + // except LaunchParams. + assertThat(params.getTask().toString()).isEqualTo(mTask.toString()); + assertThat(params.getWindowLayout().toString()).isEqualTo(mLayout.toString()); + assertThat(params.getActivity().getActivityRecord()).isSameInstanceAs(mActvity); + assertThat(params.getSource().getActivityRecord()).isSameInstanceAs(mSource); + assertThat(params.getCurrentParams().getWindowingMode()) + .isEqualTo(WINDOWING_MODE_FULLSCREEN); + assertThat(params.getOutParams().getWindowingMode()).isEqualTo(WINDOWING_MODE_MULTI_WINDOW); + assertThat(params.supportsMultiDisplay()).isEqualTo(mSupportsMultiDisplay); + } +} diff --git a/builtInServices/tests/src/com/android/server/wm/LaunchParamsWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/LaunchParamsWrapperTest.java new file mode 100644 index 0000000..3a19199 --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/LaunchParamsWrapperTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +/** + * This class contains unit tests for the {@link LaunchParamsWrapper}. + */ +public final class LaunchParamsWrapperTest { + @Test + public void create_returnsLaunchParamsWrapper() { + LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams(); + LaunchParamsWrapper wrapper = LaunchParamsWrapper.create(params); + assertThat(wrapper).isNotNull(); + } + + @Test + public void create_returnsNull() { + LaunchParamsWrapper wrapper = LaunchParamsWrapper.create(null); + assertThat(wrapper).isNull(); + } +}
\ No newline at end of file diff --git a/builtInServices/tests/src/com/android/server/wm/RequestWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/RequestWrapperTest.java new file mode 100644 index 0000000..7e81390 --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/RequestWrapperTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +/** + * This class contains unit tests for the {@link RequestWrapper}. + */ +public final class RequestWrapperTest { + @Test + public void create_returnsActivityOptionWrapper() { + ActivityStarter.Request request = new ActivityStarter.Request(); + RequestWrapper wrapper = RequestWrapper.create(request); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.getRequest()).isSameInstanceAs(request); + assertThat(wrapper.toString()).isEqualTo(request.toString()); + } + + @Test + public void create_returnsNull() { + RequestWrapper wrapper = RequestWrapper.create(null); + assertThat(wrapper).isNull(); + } +} diff --git a/builtInServices/tests/src/com/android/server/wm/TaskDisplayAreaWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/TaskDisplayAreaWrapperTest.java new file mode 100644 index 0000000..53c100f --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/TaskDisplayAreaWrapperTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * This class contains unit tests for the {@link TaskDisplayAreaWrapper}. + */ +@RunWith(MockitoJUnitRunner.class) +public final class TaskDisplayAreaWrapperTest { + @Mock + private TaskDisplayArea mTaskDisplayArea; + + @Test + public void create_returnsActivityOptionWrapper() { + TaskDisplayAreaWrapper wrapper = TaskDisplayAreaWrapper.create(mTaskDisplayArea); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.getTaskDisplayArea()).isSameInstanceAs(mTaskDisplayArea); + assertThat(wrapper.toString()).isEqualTo(mTaskDisplayArea.toString()); + } + + @Test + public void create_returnsNull() { + TaskDisplayAreaWrapper wrapper = TaskDisplayAreaWrapper.create(null); + assertThat(wrapper).isNull(); + } +} diff --git a/builtInServices/tests/src/com/android/server/wm/TaskWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/TaskWrapperTest.java new file mode 100644 index 0000000..ce29cd6 --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/TaskWrapperTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * This class contains unit tests for the {@link TaskWrapper}. + */ +@RunWith(MockitoJUnitRunner.class) +public final class TaskWrapperTest { + @Mock + private Task mTask; + + @Test + public void create_returnsActivityOptionWrapper() { + TaskWrapper wrapper = TaskWrapper.create(mTask); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.toString()).isEqualTo(mTask.toString()); + } + + @Test + public void create_returnsNull() { + TaskWrapper wrapper = TaskWrapper.create(null); + assertThat(wrapper).isNull(); + } +} diff --git a/builtInServices/tests/src/com/android/server/wm/WindowLayoutWrapperTest.java b/builtInServices/tests/src/com/android/server/wm/WindowLayoutWrapperTest.java new file mode 100644 index 0000000..0d04d69 --- /dev/null +++ b/builtInServices/tests/src/com/android/server/wm/WindowLayoutWrapperTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; + +import android.content.pm.ActivityInfo; +import android.view.Gravity; + +import org.junit.Test; + +/** + * This class contains unit tests for the {@link WindowLayoutWrapper}. + */ +public final class WindowLayoutWrapperTest { + @Test + public void create_returnsActivityOptionWrapper() { + ActivityInfo.WindowLayout layout = new ActivityInfo.WindowLayout( + /* width= */ 1280, /* widthFraction= */ 0.5f, + /* height= */ 800, /* heightFraction= */ 1.0f, + /* gravity= */ Gravity.CENTER, /* minWidth= */ 400, /* minHeight= */ 300); + WindowLayoutWrapper wrapper = WindowLayoutWrapper.create(layout); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.toString()).isEqualTo(layout.toString()); + } + + @Test + public void create_returnsNull() { + WindowLayoutWrapper wrapper = WindowLayoutWrapper.create(null); + assertThat(wrapper).isNull(); + } +} diff --git a/src/com/android/server/wm/CarLaunchParamsModifier.java b/src/com/android/server/wm/CarLaunchParamsModifier.java deleted file mode 100644 index 0997ff2..0000000 --- a/src/com/android/server/wm/CarLaunchParamsModifier.java +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) 2019 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 com.android.server.wm.ActivityStarter.Request; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.app.ActivityTaskManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.hardware.display.DisplayManager; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.util.Slog; -import android.util.SparseIntArray; -import android.view.Display; -import android.window.WindowContainerToken; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Class to control the assignment of a display for Car while launching a Activity. - * - * <p>This one controls which displays users are allowed to launch. - * The policy should be passed from car service through - * {@link com.android.internal.car.ICarServiceHelper} binder interfaces. If no policy is set, - * this module will not change anything for launch process.</p> - * - * <p> The policy can only affect which display passenger users can use. Current user, assumed - * to be a driver user, is allowed to launch any display always.</p> - */ -public final class CarLaunchParamsModifier implements LaunchParamsController.LaunchParamsModifier { - - private static final String TAG = "CAR.LAUNCH"; - private static final boolean DBG = false; - - private final Context mContext; - - private DisplayManager mDisplayManager; // set only from init() - private ActivityTaskManagerService mAtm; // set only from init() - - private final Object mLock = new Object(); - - // Always start with USER_SYSTEM as the timing of handleCurrentUserSwitching(USER_SYSTEM) is not - // guaranteed to be earler than 1st Activity launch. - @GuardedBy("mLock") - private int mCurrentDriverUser = UserHandle.USER_SYSTEM; - - // TODO: Switch from tracking displays to tracking display areas instead - /** - * This one is for holding all passenger (=profile user) displays which are mostly static unless - * displays are added / removed. Note that {@link #mDisplayToProfileUserMapping} can be empty - * while user is assigned and that cannot always tell if specific display is for driver or not. - */ - @GuardedBy("mLock") - private final ArrayList<Integer> mPassengerDisplays = new ArrayList<>(); - - /** key: display id, value: profile user id */ - @GuardedBy("mLock") - private final SparseIntArray mDisplayToProfileUserMapping = new SparseIntArray(); - - /** key: profile user id, value: display id */ - @GuardedBy("mLock") - private final SparseIntArray mDefaultDisplayForProfileUser = new SparseIntArray(); - - @GuardedBy("mLock") - private boolean mIsSourcePreferred; - - @GuardedBy("mLock") - private List<ComponentName> mSourcePreferredComponents; - - - @VisibleForTesting - final DisplayManager.DisplayListener mDisplayListener = - new DisplayManager.DisplayListener() { - @Override - public void onDisplayAdded(int displayId) { - // ignore. car service should update whiltelist. - } - - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mLock) { - mPassengerDisplays.remove(Integer.valueOf(displayId)); - updateProfileUserConfigForDisplayRemovalLocked(displayId); - } - } - - @Override - public void onDisplayChanged(int displayId) { - // ignore - } - }; - - private void updateProfileUserConfigForDisplayRemovalLocked(int displayId) { - mDisplayToProfileUserMapping.delete(displayId); - int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); - if (i >= 0) { - mDefaultDisplayForProfileUser.removeAt(i); - } - } - - /** Constructor. Can be constructed any time. */ - public CarLaunchParamsModifier(Context context) { - // This can be very early stage. So postpone interaction with other system until init. - mContext = context; - } - - /** - * Initializes all internal stuffs. This should be called only after ATMS, DisplayManagerService - * are ready. - */ - public void init() { - mAtm = (ActivityTaskManagerService) ActivityTaskManager.getService(); - LaunchParamsController controller = mAtm.mTaskSupervisor.getLaunchParamsController(); - controller.registerModifier(this); - mDisplayManager = mContext.getSystemService(DisplayManager.class); - mDisplayManager.registerDisplayListener(mDisplayListener, - new Handler(Looper.getMainLooper())); - } - - /** - * Sets sourcePreferred configuration. When sourcePreferred is enabled and there is no pre- - * assigned display for the Activity, CarLauncherParamsModifier will launch the Activity in - * the display of the source. When sourcePreferredComponents isn't null the sourcePreferred - * is applied for the sourcePreferredComponents only. - * - * @param enableSourcePreferred whether to enable sourcePreferred mode - * @param sourcePreferredComponents null for all components, or the list of components to apply - */ - public void setSourcePreferredComponents(boolean enableSourcePreferred, - @Nullable List<ComponentName> sourcePreferredComponents) { - synchronized (mLock) { - mIsSourcePreferred = enableSourcePreferred; - mSourcePreferredComponents = sourcePreferredComponents; - if (mSourcePreferredComponents != null) { - Collections.sort(mSourcePreferredComponents); - } - } - } - - /** Notifies user switching. */ - public void handleCurrentUserSwitching(int newUserId) { - synchronized (mLock) { - mCurrentDriverUser = newUserId; - mDefaultDisplayForProfileUser.clear(); - mDisplayToProfileUserMapping.clear(); - } - } - - private void removeUserFromAllowlistsLocked(int userId) { - for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) { - if (mDisplayToProfileUserMapping.valueAt(i) == userId) { - mDisplayToProfileUserMapping.removeAt(i); - } - } - mDefaultDisplayForProfileUser.delete(userId); - } - - /** Notifies user stopped. */ - public void handleUserStopped(int stoppedUser) { - // Note that the current user is never stopped. It always takes switching into - // non-current user before stopping the user. - synchronized (mLock) { - removeUserFromAllowlistsLocked(stoppedUser); - } - } - - /** - * Sets display allowlist for the userId. For passenger user, activity will be always launched - * to a display in the allowlist. If requested display is not in the allowlist, the 1st display - * in the allowlist will be selected as target display. - * - * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users - * for the given displays. - */ - public void setDisplayAllowListForUser(int userId, int[] displayIds) { - if (DBG) { - Slog.d(TAG, "setDisplayAllowlistForUser userId:" + userId - + " displays:" + displayIds); - } - synchronized (mLock) { - for (int displayId : displayIds) { - if (!mPassengerDisplays.contains(displayId)) { - Slog.w(TAG, "setDisplayAllowlistForUser called with display:" + displayId - + " not in passenger display list:" + mPassengerDisplays); - continue; - } - if (userId == mCurrentDriverUser) { - mDisplayToProfileUserMapping.delete(displayId); - } else { - mDisplayToProfileUserMapping.put(displayId, userId); - } - // now the display cannot be a default display for other user - int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); - if (i >= 0) { - mDefaultDisplayForProfileUser.removeAt(i); - } - } - if (displayIds.length > 0) { - mDefaultDisplayForProfileUser.put(userId, displayIds[0]); - } else { - removeUserFromAllowlistsLocked(userId); - } - } - } - - /** - * Sets displays assigned to passenger. All other displays will be treated as assigned to - * driver. - * - * <p>The 1st display in the array will be considered as a default display to assign - * for any non-driver user if there is no display assigned for the user. </p> - */ - public void setPassengerDisplays(int[] displayIdsForPassenger) { - if (DBG) { - Slog.d(TAG, "setPassengerDisplays displays:" + displayIdsForPassenger); - } - synchronized (mLock) { - for (int id : displayIdsForPassenger) { - mPassengerDisplays.remove(Integer.valueOf(id)); - } - // handle removed displays - for (int i = 0; i < mPassengerDisplays.size(); i++) { - int displayId = mPassengerDisplays.get(i); - updateProfileUserConfigForDisplayRemovalLocked(displayId); - } - mPassengerDisplays.clear(); - mPassengerDisplays.ensureCapacity(displayIdsForPassenger.length); - for (int id : displayIdsForPassenger) { - mPassengerDisplays.add(id); - } - } - } - - /** - * Decides display to assign while an Activity is launched. - * - * <p>For current user (=driver), launching to any display is allowed as long as system - * allows it.</p> - * - * <p>For private display, do not change anything as private display has its own logic.</p> - * - * <p>For passenger displays, only run in allowed displays. If requested display is not - * allowed, change to the 1st allowed display.</p> - */ - @Override - @Result - public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout, - @Nullable ActivityRecord activity, @Nullable ActivityRecord source, - ActivityOptions options, @Nullable Request request, int phase, - LaunchParamsController.LaunchParams currentParams, - LaunchParamsController.LaunchParams outParams) { - int userId; - if (task != null) { - userId = task.mUserId; - } else if (activity != null) { - userId = activity.mUserId; - } else { - Slog.w(TAG, "onCalculate, cannot decide user"); - return RESULT_SKIP; - } - // DisplayArea where user wants to launch the Activity. - TaskDisplayArea originalDisplayArea = currentParams.mPreferredTaskDisplayArea; - // DisplayArea where CarLaunchParamsModifier targets to launch the Activity. - TaskDisplayArea targetDisplayArea = null; - if (DBG) { - Slog.d(TAG, "onCalculate, userId:" + userId - + " original displayArea:" + originalDisplayArea - + " ActivityOptions:" + options); - } - // If originalDisplayArea is set, respect that before ActivityOptions check. - if (originalDisplayArea == null) { - if (options != null) { - WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); - if (daToken != null) { - originalDisplayArea = (TaskDisplayArea) WindowContainer.fromBinder( - daToken.asBinder()); - } else { - int originalDisplayId = options.getLaunchDisplayId(); - if (originalDisplayId != Display.INVALID_DISPLAY) { - originalDisplayArea = getDefaultTaskDisplayAreaOnDisplay(originalDisplayId); - } - } - } - } - decision: - synchronized (mLock) { - if (originalDisplayArea == null // No specified DisplayArea to launch the Activity - && mIsSourcePreferred && source != null - && (mSourcePreferredComponents == null || Collections.binarySearch( - mSourcePreferredComponents, activity.info.getComponentName()) >= 0)) { - targetDisplayArea = source.noDisplay ? source.mHandoverTaskDisplayArea - : source.getDisplayArea(); - } else if (originalDisplayArea == null - && task == null // launching as a new task - && source != null && !source.getDisplayContent().isTrusted() - && ((activity.info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0)) { - if (DBG) { - Slog.d(TAG, "Disallow launch on virtual display for not-embedded activity."); - } - targetDisplayArea = getDefaultTaskDisplayAreaOnDisplay(Display.DEFAULT_DISPLAY); - } - if (userId == mCurrentDriverUser) { - // Respect the existing DisplayArea. - break decision; - } - if (userId == UserHandle.USER_SYSTEM) { - // This will be only allowed if it has FLAG_SHOW_FOR_ALL_USERS. - // The flag is not immediately accessible here so skip the check. - // But other WM policy will enforce it. - break decision; - } - // Now user is a passenger. - if (mPassengerDisplays.isEmpty()) { - // No displays for passengers. This could be old user and do not do anything. - break decision; - } - if (targetDisplayArea == null) { - if (originalDisplayArea != null) { - targetDisplayArea = originalDisplayArea; - } else { - targetDisplayArea = getDefaultTaskDisplayAreaOnDisplay(Display.DEFAULT_DISPLAY); - } - } - Display display = targetDisplayArea.mDisplayContent.getDisplay(); - if ((display.getFlags() & Display.FLAG_PRIVATE) != 0) { - // private display should follow its own restriction rule. - break decision; - } - if (display.getType() == Display.TYPE_VIRTUAL) { - // TODO(b/132903422) : We need to update this after the bug is resolved. - // For now, don't change anything. - break decision; - } - int userForDisplay = mDisplayToProfileUserMapping.get(display.getDisplayId(), - UserHandle.USER_NULL); - if (userForDisplay == userId) { - break decision; - } - targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked( - userId, activity, request); - } - if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) { - Slog.i(TAG, "Changed launching display, user:" + userId - + " requested display area:" + originalDisplayArea - + " target display area:" + targetDisplayArea); - outParams.mPreferredTaskDisplayArea = targetDisplayArea; - return RESULT_DONE; - } else { - return RESULT_SKIP; - } - } - - @Nullable - private TaskDisplayArea getAlternativeDisplayAreaForPassengerLocked(int userId, - @NonNull ActivityRecord activityRecord, @Nullable Request request) { - TaskDisplayArea sourceDisplayArea = sourceDisplayArea(userId, activityRecord, request); - - return sourceDisplayArea != null ? sourceDisplayArea : fallbackDisplayArea(userId); - } - - @VisibleForTesting - @Nullable - TaskDisplayArea getDefaultTaskDisplayAreaOnDisplay(int displayId) { - DisplayContent dc = mAtm.mRootWindowContainer.getDisplayContentOrCreate(displayId); - if (dc == null) { - return null; - } - return dc.getDefaultTaskDisplayArea(); - } - - /** - * Calculates the {@link TaskDisplayArea} for the source of the request. The source is - * calculated implicitly from the request or the activity record. - * - * @param userId ID of the current active user - * @param activityRecord {@link ActivityRecord} that is to be shown - * @param request {@link Request} data for showing the {@link ActivityRecord} - * @return {@link TaskDisplayArea} First non {@code null} candidate display area that is allowed - * for the user. It is allowed if the display has been added to the profile mapping. - */ - @Nullable - private TaskDisplayArea sourceDisplayArea(int userId, @NonNull ActivityRecord activityRecord, - @Nullable Request request) { - List<WindowProcessController> candidateControllers = candidateControllers(activityRecord, - request); - - for (int i = 0; i < candidateControllers.size(); i++) { - WindowProcessController controller = candidateControllers.get(i); - TaskDisplayArea candidate = controller.getTopActivityDisplayArea(); - int displayId = candidate != null ? candidate.getDisplayId() : Display.INVALID_DISPLAY; - int userForDisplay = mDisplayToProfileUserMapping.get(displayId, UserHandle.USER_NULL); - if (userForDisplay == userId) { - return candidate; - } - } - return null; - } - - /** - * Calculates a list of {@link WindowProcessController} that can calculate the - * {@link TaskDisplayArea} to house the {@link ActivityRecord}. Controllers are calculated since - * calculating the display can be expensive. The list is ordered in the - * following way - * <ol> - * <li>Controller for the activity record from the process name and app uid</li> - * <li>Controller for the activity that is launching the given record</li> - * <li>Controller for the actual process that is launching the record</li> - * </ol> - * - * @param activityRecord {@link ActivityRecord} that is to be shown - * @param request {@link Request} data for showing the {@link ActivityRecord} - * @return {@link List} of {@link WindowProcessController} ordered by preference to be shown - */ - private List<WindowProcessController> candidateControllers( - @NonNull ActivityRecord activityRecord, @Nullable Request request) { - WindowProcessController firstController = mAtm.getProcessController( - activityRecord.getProcessName(), activityRecord.getUid()); - - WindowProcessController secondController = mAtm.getProcessController( - activityRecord.getLaunchedFromPid(), activityRecord.getLaunchedFromUid()); - - WindowProcessController thirdController = request == null ? null : - mAtm.getProcessController(request.realCallingPid, request.realCallingUid); - - List<WindowProcessController> candidates = new ArrayList<>(3); - - if (firstController != null) { - candidates.add(firstController); - } - if (secondController != null) { - candidates.add(secondController); - } - if (thirdController != null) { - candidates.add(thirdController); - } - - return candidates; - } - - /** - * Return a {@link TaskDisplayArea} that can be used if a source display area is not found. - * First check the default display for the user. If it is absent select the first passenger - * display if present. If both are absent return {@code null} - * - * @param userId ID of the active user - * @return {@link TaskDisplayArea} that is recommended when a display area is not specified - */ - @Nullable - private TaskDisplayArea fallbackDisplayArea(int userId) { - int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId, - Display.INVALID_DISPLAY); - if (displayIdForUserProfile != Display.INVALID_DISPLAY) { - int displayId = mDefaultDisplayForProfileUser.get(userId); - return getDefaultTaskDisplayAreaOnDisplay(displayId); - } - - if (!mPassengerDisplays.isEmpty()) { - int displayId = mPassengerDisplays.get(0); - return getDefaultTaskDisplayAreaOnDisplay(displayId); - } - - return null; - } - -} diff --git a/src/jni/com_android_internal_car_CarServiceHelperService.cpp b/src/jni/com_android_internal_car_CarServiceHelperService.cpp deleted file mode 100644 index 3942b77..0000000 --- a/src/jni/com_android_internal_car_CarServiceHelperService.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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. - */ - -#define LOG_TAG "CarServiceHelperService-JNI" - -//#define LOG_NDEBUG 0 - -#include "jni.h" - -#include <nativehelper/JNIHelp.h> -#include <suspend/autosuspend.h> -#include <utils/Log.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -static jint nativeForceSuspend(JNIEnv* /* env */, jclass /* clazz */, jint timeoutMs) { - jint ret = autosuspend_force_suspend(timeoutMs); - ALOGD("nativeForceSuspend returned %d", ret); - return ret; -} - -// ---------------------------------------------------------------------------- - -static const JNINativeMethod gCarServiceHelperServiceMethods[] = { - /* name, signature, funcPtr */ - { "nativeForceSuspend", "(I)I", - (void*) nativeForceSuspend }, -}; - -int register_android_internal_car_CarServiceHelperService(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, "com/android/internal/car/CarServiceHelperService", - gCarServiceHelperServiceMethods, NELEM(gCarServiceHelperServiceMethods)); - (void) res; // Faked use when LOG_NDEBUG. - LOG_FATAL_IF(res < 0, "Unable to register native methods."); - return 0; -} - -} /* namespace android */ - - -using namespace android; - -extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) -{ - JNIEnv* env = NULL; - jint result = -1; - - if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { - ALOGE("GetEnv failed!"); - return result; - } - ALOG_ASSERT(env, "Could not retrieve the env!"); - - register_android_internal_car_CarServiceHelperService(env); - return JNI_VERSION_1_4; -} diff --git a/updatableServices/Android.bp b/updatableServices/Android.bp new file mode 100644 index 0000000..2adcba7 --- /dev/null +++ b/updatableServices/Android.bp @@ -0,0 +1,31 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "car-frameworks-service-module", + installable: true, + libs: [ + "android.car", + "android.car.builtin", + "car-frameworks-service", + "framework-annotations-lib", + "modules-utils-preconditions", + ], + srcs: [ + "src/**/*.java", + ], + + sdk_version: "module_current", + min_sdk_version: "31", + apex_available: [ + "//apex_available:platform", + "com.android.car.framework" + ], + product_variables: { + pdk: { + enabled: false, + }, + }, +} + diff --git a/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java b/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java new file mode 100644 index 0000000..0b3b094 --- /dev/null +++ b/updatableServices/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImpl.java @@ -0,0 +1,335 @@ +/* + * 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 com.android.internal.car.updatable; + +import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT; +import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE; + +import android.annotation.Nullable; +import android.car.ICar; +import android.car.ICarResultReceiver; +import android.car.builtin.os.UserManagerHelper; +import android.car.builtin.util.EventLogHelper; +import android.car.builtin.util.Slogf; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; + +import com.android.car.internal.ICarServiceHelper; +import com.android.car.internal.ICarSystemServerClient; +import com.android.car.internal.util.IndentingPrintWriter; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.car.CarServiceHelperInterface; +import com.android.internal.car.CarServiceHelperServiceUpdatable; +import java.io.File; +import com.android.server.wm.CarLaunchParamsModifierInterface; +import com.android.server.wm.CarLaunchParamsModifierUpdatable; +import com.android.server.wm.CarLaunchParamsModifierUpdatableImpl; + +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; + +/** + * Implementation of the abstract class CarServiceHelperUpdatable + */ +public final class CarServiceHelperServiceUpdatableImpl + implements CarServiceHelperServiceUpdatable, Executor { + + @VisibleForTesting + static final String TAG = "CarServiceHelper"; + + private static final boolean DBG = false; + + private static final String PROP_RESTART_RUNTIME = "ro.car.recovery.restart_runtime.enabled"; + + private static final long CAR_SERVICE_BINDER_CALL_TIMEOUT_MS = 15_000; + + private final Runnable mCallbackForCarServiceUnresponsiveness; + + // exit code for + private static final int STATUS_CODE_To_EXIT = 10; + + private static final String CAR_SERVICE_PACKAGE = "com.android.car"; + + private final Context mContext; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private ICar mCarServiceBinder; + + private final Handler mHandler; + private final HandlerThread mHandlerThread = new HandlerThread( + CarServiceHelperServiceUpdatableImpl.class.getSimpleName()); + + private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl(); + + private final CarServiceConnectedCallback mCarServiceConnectedCallback = + new CarServiceConnectedCallback(); + + private final CarServiceProxy mCarServiceProxy; + + private final CarServiceHelperInterface mCarServiceHelperInterface; + + private final CarLaunchParamsModifierUpdatableImpl mCarLaunchParamsModifierUpdatable; + + public CarServiceHelperServiceUpdatableImpl(Context context, + CarServiceHelperInterface carServiceHelperInterface, + CarLaunchParamsModifierInterface carLaunchParamsModifierInterface) { + this(context, carServiceHelperInterface, carLaunchParamsModifierInterface, + /* carServiceProxy= */ null); + } + + @VisibleForTesting + CarServiceHelperServiceUpdatableImpl(Context context, + CarServiceHelperInterface carServiceHelperInterface, + CarLaunchParamsModifierInterface carLaunchParamsModifierInterface, + @Nullable CarServiceProxy carServiceProxy) { + mContext = context; + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mCarServiceHelperInterface = carServiceHelperInterface; + mCarLaunchParamsModifierUpdatable = new CarLaunchParamsModifierUpdatableImpl( + carLaunchParamsModifierInterface); + // carServiceProxy is Nullable because it is not possible to construct carServiceProxy with + // "this" object in the previous constructor as CarServiceHelperServiceUpdatableImpl has + // not been fully constructed. + mCarServiceProxy = carServiceProxy == null ? new CarServiceProxy(this) : carServiceProxy; + mCallbackForCarServiceUnresponsiveness = () -> handleCarServiceUnresponsive(); + } + + private final ServiceConnection mCarServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + if (DBG) Slogf.d(TAG, "onServiceConnected: %s", iBinder); + handleCarServiceConnection(iBinder); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + handleCarServiceCrash(); + } + }; + + @Override + public void onStart() { + Intent intent = new Intent(CAR_SERVICE_INTERFACE).setPackage(CAR_SERVICE_PACKAGE); + Context userContext = mContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0); + if (!userContext.bindService(intent, Context.BIND_AUTO_CREATE, this, + mCarServiceConnection)) { + Slogf.wtf(TAG, "cannot start car service"); + } + } + + @Override // From Executor + public void execute(Runnable command) { + mHandler.post(command); + } + + @Override + public void onUserRemoved(UserHandle user) { + mCarServiceProxy.onUserRemoved(user); + } + + @Override + public void onFactoryReset(BiConsumer<Integer, Bundle> callback) { + ICarResultReceiver resultReceiver = new ICarResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + callback.accept(resultCode, resultData); + + } + }; + mCarServiceProxy.onFactoryReset(resultReceiver); + } + + @Override + public void initBootUser() { + mCarServiceProxy.initBootUser(); + } + + @Override + public CarLaunchParamsModifierUpdatable getCarLaunchParamsModifierUpdatable() { + return mCarLaunchParamsModifierUpdatable; + } + + @VisibleForTesting + void handleCarServiceConnection(IBinder iBinder) { + synchronized (mLock) { + if (mCarServiceBinder == ICar.Stub.asInterface(iBinder)) { + return; // already connected. + } + if (DBG) { + Slogf.d(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, + iBinder); + } + mCarServiceBinder = ICar.Stub.asInterface(iBinder); + Slogf.i(TAG, "**CarService connected**"); + } + + EventLogHelper.writeCarHelperServiceConnected(); + + // Post mCallbackForCarServiceUnresponsiveness before setting system server connection + // because CarService may respond before the sendSetSystemServerConnectionsCall call + // returns and try to remove mCallbackForCarServiceUnresponsiveness from the handler. + // Thus, posting this callback after setting system server connection may result in a race + // condition where the callback is never removed from the handler. + mHandler.removeCallbacks(mCallbackForCarServiceUnresponsiveness); + mHandler.postDelayed(mCallbackForCarServiceUnresponsiveness, + CAR_SERVICE_BINDER_CALL_TIMEOUT_MS); + + sendSetSystemServerConnectionsCall(); + } + + @VisibleForTesting + void handleCarServiceCrash() { + // Recovery behavior. Kill the system server and reset + // everything if enabled by the property. + boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false); + mHandler.removeCallbacks(mCallbackForCarServiceUnresponsiveness); + + mCarServiceHelperInterface.dumpServiceStacks(); + if (restartOnServiceCrash) { + Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService crash"); + Slogf.w(TAG, "*** GOODBYE!"); + Process.killProcess(Process.myPid()); + System.exit(STATUS_CODE_To_EXIT); + } else { + Slogf.w(TAG, "*** CARHELPER ignoring: CarService crash"); + } + } + + private void sendSetSystemServerConnectionsCall() { + ICar binder; + synchronized (mLock) { + binder = mCarServiceBinder; + } + try { + binder.setSystemServerConnections(mHelper, mCarServiceConnectedCallback); + } catch (RemoteException e) { + Slogf.w(TAG, e, "RemoteException from car service"); + handleCarServiceCrash(); + } catch (RuntimeException e) { + Slogf.wtf(TAG, e, "Exception calling setSystemServerConnections"); + throw e; + } + } + + private void handleCarServiceUnresponsive() { + // This should not happen. Calling this method means ICarSystemServerClient binder is not + // returned after service connection. and CarService has not connected in the given time. + Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService unresponsive."); + Slogf.w(TAG, "*** GOODBYE!"); + Process.killProcess(Process.myPid()); + System.exit(STATUS_CODE_To_EXIT); + } + + @Override + public void sendUserLifecycleEvent(int eventType, UserHandle userFrom, UserHandle userTo) { + mCarServiceProxy.sendUserLifecycleEvent(eventType, + userFrom == null ? UserManagerHelper.USER_NULL : userFrom.getIdentifier(), + userTo.getIdentifier()); + } + + @Override + public void dump(PrintWriter writer, String[] args) { + if (args != null && args.length > 0 && "--user-metrics-only".equals(args[0])) { + mCarServiceProxy.dumpUserMetrics(new IndentingPrintWriter(writer)); + return; + } + + if (args != null && args.length > 0 && "--dump-service-stacks".equals(args[0])) { + File file = mCarServiceHelperInterface.dumpServiceStacks(); + if (file != null) { + writer.printf("dumpServiceStacks ANR file path=%s\n", file.getAbsolutePath()); + } else { + writer.printf("dumpServiceStacks no ANR file.\n"); + } + return; + } + + mCarServiceProxy.dump(new IndentingPrintWriter(writer)); + } + + private final class ICarServiceHelperImpl extends ICarServiceHelper.Stub { + + @Override + public void setDisplayAllowlistForUser(int userId, int[] displayIds) { + mCarLaunchParamsModifierUpdatable.setDisplayAllowListForUser(userId, displayIds); + } + + @Override + public void setPassengerDisplays(int[] displayIdsForPassenger) { + mCarLaunchParamsModifierUpdatable.setPassengerDisplays(displayIdsForPassenger); + } + + @Override + public void setSourcePreferredComponents(boolean enableSourcePreferred, + @Nullable List<ComponentName> sourcePreferredComponents) { + mCarLaunchParamsModifierUpdatable.setSourcePreferredComponents( + enableSourcePreferred, sourcePreferredComponents); + } + + @Override + public int setPersistentActivity(ComponentName activity, int displayId, int featureId) { + return mCarLaunchParamsModifierUpdatable.setPersistentActivity( + activity, displayId, featureId); + } + + @Override + public void setSafetyMode(boolean safe) { + mCarServiceHelperInterface.setSafetyMode(safe); + } + + @Override + public UserHandle createUserEvenWhenDisallowed(String name, String userType, int flags) { + return mCarServiceHelperInterface.createUserEvenWhenDisallowed(name, userType, flags); + } + + @Override + public void sendInitialUser(UserHandle user) { + mCarServiceProxy.saveInitialUser(user); + } + } + + private final class CarServiceConnectedCallback extends ICarResultReceiver.Stub { + @Override + public void send(int resultCode, Bundle resultData) { + mHandler.removeCallbacks(mCallbackForCarServiceUnresponsiveness); + + IBinder binder; + if (resultData == null + || (binder = resultData.getBinder(ICAR_SYSTEM_SERVER_CLIENT)) == null) { + Slogf.wtf(TAG, "setSystemServerConnections return NULL data or Binder."); + handleCarServiceUnresponsive(); + return; + } + + ICarSystemServerClient carService = ICarSystemServerClient.Stub.asInterface(binder); + mCarServiceProxy.handleCarServiceConnection(carService); + } + } +} diff --git a/src/com/android/internal/car/CarServiceProxy.java b/updatableServices/src/com/android/internal/car/updatable/CarServiceProxy.java index 42c344b..0824755 100644 --- a/src/com/android/internal/car/CarServiceProxy.java +++ b/updatableServices/src/com/android/internal/car/updatable/CarServiceProxy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.car; +package com.android.internal.car.updatable; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED; @@ -27,23 +27,22 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.content.pm.UserInfo; +import android.car.ICarResultReceiver; +import android.car.builtin.os.UserManagerHelper; +import android.car.builtin.util.Slogf; +import android.car.builtin.util.TimingsTraceLog; import android.os.RemoteException; -import android.os.Trace; import android.os.UserHandle; -import android.util.DebugUtils; -import android.util.IndentingPrintWriter; -import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.car.internal.ICarSystemServerClient; import com.android.car.internal.common.CommonConstants.UserLifecycleEventType; +import com.android.car.internal.util.DebugUtils; +import com.android.car.internal.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.IResultReceiver; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; -import com.android.server.SystemService.TargetUser; -import com.android.server.utils.TimingsTraceAndSlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -89,10 +88,13 @@ final class CarServiceProxy { @Retention(RetentionPolicy.SOURCE) public @interface PendingOperationId{} + @VisibleForTesting + static final String TAG = CarServiceProxy.class.getSimpleName(); + + private static final long TRACE_TAG_SYSTEM_SERVER = 1L << 19; private static final boolean DBG = false; - private static final String TAG = CarServiceProxy.class.getSimpleName(); - private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0; + private static final int USER_SYSTEM = UserHandle.SYSTEM.getIdentifier(); private final Object mLock = new Object(); @@ -100,10 +102,10 @@ final class CarServiceProxy { private boolean mCarServiceCrashed; @UserIdInt @GuardedBy("mLock") - private int mLastSwitchedUser = UserHandle.USER_NULL; + private int mLastSwitchedUser = UserManagerHelper.USER_NULL; @UserIdInt @GuardedBy("mLock") - private int mPreviousUserOfLastSwitchedUser = UserHandle.USER_NULL; + private int mPreviousUserOfLastSwitchedUser = UserManagerHelper.USER_NULL; // Key: user id, value: life-cycle @GuardedBy("mLock") private final SparseIntArray mLastUserLifecycle = new SparseIntArray(); @@ -114,19 +116,21 @@ final class CarServiceProxy { @GuardedBy("mLock") private ICarSystemServerClient mCarService; - private final CarServiceHelperService mCarServiceHelperService; + private final CarServiceHelperServiceUpdatableImpl mCarServiceHelperServiceUpdatableImpl; private final UserMetrics mUserMetrics = new UserMetrics(); + @GuardedBy("mLock") + private UserHandle mInitialUser; - CarServiceProxy(CarServiceHelperService carServiceHelperService) { - mCarServiceHelperService = carServiceHelperService; + CarServiceProxy(CarServiceHelperServiceUpdatableImpl carServiceHelperServiceUpdatableImpl) { + mCarServiceHelperServiceUpdatableImpl = carServiceHelperServiceUpdatableImpl; } /** * Handles new CarService Connection. */ void handleCarServiceConnection(ICarSystemServerClient carService) { - Slog.i(TAG, "CarService connected."); - TimingsTraceAndSlog t = newTimingsTraceAndSlog(); + Slogf.i(TAG, "CarService connected."); + TimingsTraceLog t = newTimingsTraceLog(); t.traceBegin("handleCarServiceConnection"); synchronized (mLock) { mCarService = carService; @@ -136,9 +140,37 @@ final class CarServiceProxy { runQueuedOperationLocked(PO_ON_FACTORY_RESET); } sendLifeCycleEvents(); + sendInitialUser(); t.traceEnd(); } + private void sendInitialUser() { + UserHandle initialUser; + ICarSystemServerClient carService; + synchronized (mLock) { + initialUser = mInitialUser; + carService = mCarService; + } + if (initialUser != null && carService != null) { + try { + carService.setInitialUser(initialUser); + } catch (RemoteException e) { + Slogf.w(TAG, "RemoteException from car service while calling setInitialUser.", e); + } + } else { + Slogf.i(TAG, "Didn't send Initial User, User: %s, CarService: %s", initialUser, + carService); + } + } + + void saveInitialUser(UserHandle user) { + synchronized (mLock) { + if (user != null || user.getIdentifier() != UserManagerHelper.USER_NULL) { + mInitialUser = user; + } + } + } + @GuardedBy("mLock") private void runQueuedOperationLocked(@PendingOperationId int operationId) { PendingOperation pendingOperation = mPendingOperations.get(operationId); @@ -147,7 +179,7 @@ final class CarServiceProxy { return; } if (DBG) { - Slog.d(TAG, "No queued operation of type " + pendingOperationToString(operationId)); + Slogf.d(TAG, "No queued operation of type " + pendingOperationToString(operationId)); } } @@ -161,13 +193,14 @@ final class CarServiceProxy { } // Send user0 events first - int user0Lifecycle = lastUserLifecycle.get(UserHandle.USER_SYSTEM); - boolean user0IsCurrent = lastSwitchedUser == UserHandle.USER_SYSTEM; + int user0Lifecycle = lastUserLifecycle.get(USER_SYSTEM); + boolean user0IsCurrent = lastSwitchedUser == USER_SYSTEM; // If user0Lifecycle is 0, then no life-cycle event received yet. if (user0Lifecycle != 0) { - sendAllLifecyleToUser(UserHandle.USER_SYSTEM, user0Lifecycle, user0IsCurrent); + sendAllLifecyleToUser(USER_SYSTEM, user0Lifecycle, + user0IsCurrent); } - lastUserLifecycle.delete(UserHandle.USER_SYSTEM); + lastUserLifecycle.delete(USER_SYSTEM); // Send current user events next if (!user0IsCurrent) { @@ -192,28 +225,28 @@ final class CarServiceProxy { private void sendAllLifecyleToUser(@UserIdInt int userId, int lifecycle, boolean isCurrentUser) { if (DBG) { - Slog.d(TAG, "sendAllLifecyleToUser, user:" + userId + " lifecycle:" + lifecycle); + Slogf.d(TAG, "sendAllLifecyleToUser, user:" + userId + " lifecycle:" + lifecycle); } if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_STARTING) { - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, UserHandle.USER_NULL, - userId); + sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_STARTING, + UserManagerHelper.USER_NULL, userId); } - if (isCurrentUser && userId != UserHandle.USER_SYSTEM) { + if (isCurrentUser && userId != USER_SYSTEM) { synchronized (mLock) { - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, + sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, mPreviousUserOfLastSwitchedUser, userId); } } if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) { - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, UserHandle.USER_NULL, - userId); + sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, + UserManagerHelper.USER_NULL, userId); } if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) { - sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, UserHandle.USER_NULL, - userId); + sendUserLifecycleEventInternal(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, + UserManagerHelper.USER_NULL, userId); } } @@ -221,7 +254,7 @@ final class CarServiceProxy { * Initializes boot user. */ void initBootUser() { - if (DBG) Slog.d(TAG, "initBootUser()"); + if (DBG) Slogf.d(TAG, "initBootUser()"); saveOrRun(PO_INIT_BOOT_USER); } @@ -230,8 +263,8 @@ final class CarServiceProxy { /** * Callback to indifcate the given user was removed. */ - void onUserRemoved(@NonNull UserInfo user) { - if (DBG) Slog.d(TAG, "onUserRemoved(): " + user.toFullString()); + void onUserRemoved(@NonNull UserHandle user) { + if (DBG) Slogf.d(TAG, "onUserRemoved(): " + user); saveOrRun(PO_ON_USER_REMOVED, user); } @@ -240,8 +273,8 @@ final class CarServiceProxy { /** * Callback to ask user to confirm if it's ok to factory reset the device. */ - void onFactoryReset(@NonNull IResultReceiver callback) { - if (DBG) Slog.d(TAG, "onFactoryReset(): " + callback); + void onFactoryReset(@NonNull ICarResultReceiver callback) { + if (DBG) Slogf.d(TAG, "onFactoryReset(): " + callback); saveOrRun(PO_ON_FACTORY_RESET, callback); } @@ -254,7 +287,7 @@ final class CarServiceProxy { synchronized (mLock) { if (mCarService == null) { if (DBG) { - Slog.d(TAG, "CarService null. Operation " + Slogf.d(TAG, "CarService null. Operation " + pendingOperationToString(operationId) + (value == null ? "" : "(" + value + ")") + " deferred."); } @@ -272,20 +305,22 @@ final class CarServiceProxy { @GuardedBy("mLock") private void runLocked(@PendingOperationId int operationId, @Nullable Object value) { - if (DBG) Slog.d(TAG, "runLocked(): " + pendingOperationToString(operationId) + "/" + value); + if (DBG) { + Slogf.d(TAG, "runLocked(): " + pendingOperationToString(operationId) + "/" + value); + } try { if (isServiceCrashedLoggedLocked(operationId)) { return; } sendCarServiceActionLocked(operationId, value); if (operationId == PO_ON_FACTORY_RESET) { - if (DBG) Slog.d(TAG, "NOT removing " + pendingOperationToString(operationId)); + if (DBG) Slogf.d(TAG, "NOT removing " + pendingOperationToString(operationId)); return; } - if (DBG) Slog.d(TAG, "removing " + pendingOperationToString(operationId)); + if (DBG) Slogf.d(TAG, "removing " + pendingOperationToString(operationId)); mPendingOperations.delete(operationId); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException from car service", e); + Slogf.w(TAG, "RemoteException from car service", e); handleCarServiceCrash(); } } @@ -297,22 +332,22 @@ final class CarServiceProxy { if (pendingOperation == null) { pendingOperation = new PendingOperation(operationId, value); - if (DBG) Slog.d(TAG, "Created " + pendingOperation); + if (DBG) Slogf.d(TAG, "Created " + pendingOperation); mPendingOperations.put(operationId, pendingOperation); return; } switch (operationId) { case PO_ON_USER_REMOVED: - Preconditions.checkArgument((value instanceof UserInfo), + Preconditions.checkArgument((value instanceof UserHandle), "invalid value passed to ON_USER_REMOVED", value); if (pendingOperation.value instanceof ArrayList) { - if (DBG) Slog.d(TAG, "Adding " + value + " to existing " + pendingOperation); + if (DBG) Slogf.d(TAG, "Adding " + value + " to existing " + pendingOperation); ((ArrayList) pendingOperation.value).add(value); - } else if (pendingOperation.value instanceof UserInfo) { + } else if (pendingOperation.value instanceof UserHandle) { ArrayList<Object> list = new ArrayList<>(2); list.add(pendingOperation.value); list.add(value); - if (DBG) Slog.d(TAG, "Converting " + pendingOperation.value + " to " + list); + if (DBG) Slogf.d(TAG, "Converting " + pendingOperation.value + " to " + list); pendingOperation.value = list; } else { throw new IllegalStateException("Invalid value for ON_USER_REMOVED: " + value); @@ -320,12 +355,12 @@ final class CarServiceProxy { break; case PO_ON_FACTORY_RESET: PendingOperation newOperation = new PendingOperation(operationId, value); - if (DBG) Slog.d(TAG, "Replacing " + pendingOperation + " by " + newOperation); + if (DBG) Slogf.d(TAG, "Replacing " + pendingOperation + " by " + newOperation); mPendingOperations.put(operationId, newOperation); break; default: if (DBG) { - Slog.d(TAG, "Already saved operation of type " + Slogf.d(TAG, "Already saved operation of type " + pendingOperationToString(operationId)); } } @@ -335,7 +370,7 @@ final class CarServiceProxy { private void sendCarServiceActionLocked(@PendingOperationId int operationId, @Nullable Object value) throws RemoteException { if (DBG) { - Slog.d(TAG, "sendCarServiceActionLocked: Operation " + Slogf.d(TAG, "sendCarServiceActionLocked: Operation " + pendingOperationToString(operationId)); } switch (operationId) { @@ -345,7 +380,7 @@ final class CarServiceProxy { case PO_ON_USER_REMOVED: if (value instanceof ArrayList) { ArrayList<Object> list = (ArrayList<Object>) value; - if (DBG) Slog.d(TAG, "Sending " + list.size() + " onUserRemoved() calls"); + if (DBG) Slogf.d(TAG, "Sending " + list.size() + " onUserRemoved() calls"); for (Object user: list) { onUserRemovedLocked(user); } @@ -354,58 +389,56 @@ final class CarServiceProxy { } break; case PO_ON_FACTORY_RESET: - mCarService.onFactoryReset((IResultReceiver) value); + mCarService.onFactoryReset((ICarResultReceiver) value); break; default: - Slog.wtf(TAG, "Invalid Operation. OperationId -" + operationId); + Slogf.wtf(TAG, "Invalid Operation. OperationId -" + operationId); } } @GuardedBy("mLock") private void onUserRemovedLocked(@NonNull Object value) throws RemoteException { - Preconditions.checkArgument((value instanceof UserInfo), + Preconditions.checkArgument((value instanceof UserHandle), "Invalid value for ON_USER_REMOVED: %s", value); - UserInfo user = (UserInfo) value; - if (DBG) Slog.d(TAG, "Sending onUserRemoved(): " + user.toFullString()); + UserHandle user = (UserHandle) value; + if (DBG) Slogf.d(TAG, "Sending onUserRemoved(): " + user); mCarService.onUserRemoved(user); } /** * Sends user life-cycle events to CarService. */ - void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, @Nullable TargetUser from, - @NonNull TargetUser to) { + void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, @UserIdInt int fromId, + @UserIdInt int toId) { long now = System.currentTimeMillis(); - int fromId = from == null ? UserHandle.USER_NULL : from.getUserIdentifier(); - int toId = to.getUserIdentifier(); mUserMetrics.onEvent(eventType, now, fromId, toId); synchronized (mLock) { if (eventType == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) { - mLastSwitchedUser = to.getUserIdentifier(); - mPreviousUserOfLastSwitchedUser = from.getUserIdentifier(); - mLastUserLifecycle.put(to.getUserIdentifier(), eventType); + mLastSwitchedUser = toId; + mPreviousUserOfLastSwitchedUser = fromId; + mLastUserLifecycle.put(toId, eventType); } else if (eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPING || eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPED) { - mLastUserLifecycle.delete(to.getUserIdentifier()); + mLastUserLifecycle.delete(toId); } else { - mLastUserLifecycle.put(to.getUserIdentifier(), eventType); + mLastUserLifecycle.put(toId, eventType); } if (mCarService == null) { if (DBG) { - Slog.d(TAG, "CarService null. sendUserLifecycleEvent() deferred for lifecycle" - + " event " + eventType + " for user " + to); + Slogf.d(TAG, "CarService null. sendUserLifecycleEvent() deferred for lifecycle" + + " event " + eventType + " for user " + toId); } return; } } - sendUserLifecycleEvent(eventType, fromId, toId); + sendUserLifecycleEventInternal(eventType, fromId, toId); } - private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, + private void sendUserLifecycleEventInternal(@UserLifecycleEventType int eventType, @UserIdInt int fromId, @UserIdInt int toId) { if (DBG) { - Slog.d(TAG, "sendUserLifecycleEvent():" + " eventType=" + eventType + ", fromId=" + Slogf.d(TAG, "sendUserLifecycleEvent():" + " eventType=" + eventType + ", fromId=" + fromId + ", toId=" + toId); } try { @@ -414,7 +447,7 @@ final class CarServiceProxy { mCarService.onUserLifecycleEvent(eventType, fromId, toId); } } catch (RemoteException e) { - Slog.w(TAG, "RemoteException from car service", e); + Slogf.w(TAG, "RemoteException from car service", e); handleCarServiceCrash(); } } @@ -424,12 +457,12 @@ final class CarServiceProxy { mCarServiceCrashed = true; mCarService = null; } - Slog.w(TAG, "CarServiceCrashed. No more car service calls before reconnection."); - mCarServiceHelperService.handleCarServiceCrash(); + Slogf.w(TAG, "CarServiceCrashed. No more car service calls before reconnection."); + mCarServiceHelperServiceUpdatableImpl.handleCarServiceCrash(); } - private TimingsTraceAndSlog newTimingsTraceAndSlog() { - return new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER); + private TimingsTraceLog newTimingsTraceLog() { + return new TimingsTraceLog(TAG, TRACE_TAG_SYSTEM_SERVER); } @GuardedBy("mLock") @@ -440,7 +473,8 @@ final class CarServiceProxy { @GuardedBy("mLock") private boolean isServiceCrashedLoggedLocked(@NonNull String operation) { if (mCarServiceCrashed) { - Slog.w(TAG, "CarServiceCrashed. " + operation + " will be executed after reconnection"); + Slogf.w(TAG, "CarServiceCrashed. " + operation + " will be executed after " + + "reconnection"); return true; } return false; @@ -450,33 +484,37 @@ final class CarServiceProxy { * Dump */ void dump(IndentingPrintWriter writer) { + // Do not change the next line, Used in cts test: testCarServiceHelperServiceDump writer.println("CarServiceProxy"); writer.increaseIndent(); - writer.printf("mLastSwitchedUser=%s\n", mLastSwitchedUser); - writer.printf("mLastUserLifecycle:\n"); - int user0Lifecycle = mLastUserLifecycle.get(UserHandle.USER_SYSTEM, 0); - if (user0Lifecycle != 0) { - writer.printf("SystemUser Lifecycle Event:%s\n", user0Lifecycle); - } else { - writer.println("SystemUser not initialized"); - } + synchronized (mLock) { + writer.printf("mLastSwitchedUser=%s\n", mLastSwitchedUser); + writer.printf("mInitialUser=%s\n", mInitialUser); + writer.printf("mLastUserLifecycle:\n"); + int user0Lifecycle = mLastUserLifecycle.get(USER_SYSTEM, 0); + if (user0Lifecycle != 0) { + writer.printf("SystemUser Lifecycle Event:%s\n", user0Lifecycle); + } else { + writer.println("SystemUser not initialized"); + } - int lastUserLifecycle = mLastUserLifecycle.get(mLastSwitchedUser, 0); - if (mLastSwitchedUser != UserHandle.USER_SYSTEM && user0Lifecycle != 0) { - writer.printf("last user (%s) Lifecycle Event:%s\n", - mLastSwitchedUser, lastUserLifecycle); - } + int lastUserLifecycle = mLastUserLifecycle.get(mLastSwitchedUser, 0); + if (mLastSwitchedUser != USER_SYSTEM && user0Lifecycle != 0) { + writer.printf("last user (%s) Lifecycle Event:%s\n", + mLastSwitchedUser, lastUserLifecycle); + } - int size = mPendingOperations.size(); - if (size == 0) { - writer.println("No pending operations"); - } else { - writer.printf("%d pending operation%s:\n", size, size == 1 ? "" : "s"); - writer.increaseIndent(); - for (int i = 0; i < size; i++) { - writer.println(mPendingOperations.valueAt(i)); + int size = mPendingOperations.size(); + if (size == 0) { + writer.println("No pending operations"); + } else { + writer.printf("%d pending operation%s:\n", size, size == 1 ? "" : "s"); + writer.increaseIndent(); + for (int i = 0; i < size; i++) { + writer.println(mPendingOperations.valueAt(i)); + } + writer.decreaseIndent(); } - writer.decreaseIndent(); } writer.decreaseIndent(); dumpUserMetrics(writer); diff --git a/src/com/android/internal/car/UserMetrics.java b/updatableServices/src/com/android/internal/car/updatable/UserMetrics.java index ff5fa75..31a4bfd 100644 --- a/src/com/android/internal/car/UserMetrics.java +++ b/updatableServices/src/com/android/internal/car/updatable/UserMetrics.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.car; +package com.android.internal.car.updatable; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED; @@ -26,13 +26,13 @@ import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVE import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.util.IndentingPrintWriter; -import android.util.LocalLog; -import android.util.Slog; +import android.car.builtin.util.Slogf; +import android.car.builtin.util.TimeUtils; import android.util.SparseArray; -import android.util.TimeUtils; import com.android.car.internal.common.CommonConstants.UserLifecycleEventType; +import com.android.car.internal.util.IndentingPrintWriter; +import com.android.car.internal.util.LocalLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -105,7 +105,7 @@ final class UserMetrics { onUserStoppedEventLocked(timestampMs, toUserId); return; default: - Slog.w(TAG, "Invalid event: " + eventType); + Slogf.w(TAG, "Invalid event: " + eventType); } } } @@ -131,7 +131,7 @@ final class UserMetrics { UserStartingMetric existingMetrics = mUserStartingMetrics.get(userId); if (existingMetrics != null) { - Slog.w(TAG, "user re-started: " + existingMetrics); + Slogf.w(TAG, "user re-started: " + existingMetrics); finishUserStartingLocked(existingMetrics, /* removeMetric= */ false); } @@ -169,7 +169,7 @@ final class UserMetrics { } UserStoppingMetric existingMetrics = mUserStoppingMetrics.get(userId); if (existingMetrics != null) { - Slog.w(TAG, "user re-stopped: " + existingMetrics); + Slogf.w(TAG, "user re-stopped: " + existingMetrics); finishUserStoppingLocked(existingMetrics, /* removeMetric= */ false); } mUserStoppingMetrics.put(userId, new UserStoppingMetric(userId, timestampMs)); @@ -187,14 +187,15 @@ final class UserMetrics { private <T extends BaseUserMetric> T getExistingMetricsLocked( @NonNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId) { if (metrics == null) { - Slog.w(TAG, "getExistingMetricsLocked() should not pass null metrics, except on tests"); + Slogf.w(TAG, "getExistingMetricsLocked() should not pass null metrics, except on " + + "tests"); return null; } @SuppressWarnings("unchecked") T metric = (T) metrics.get(userId); if (metric == null) { String name = metrics == mUserStartingMetrics ? "starting" : "stopping"; - Slog.w(TAG, "no " + name + " metrics for user " + userId); + Slogf.w(TAG, "no " + name + " metrics for user " + userId); } return metric; } diff --git a/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java b/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java new file mode 100644 index 0000000..d836c03 --- /dev/null +++ b/updatableServices/src/com/android/server/wm/CarLaunchParamsModifierUpdatableImpl.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2022 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.UserIdInt; +import android.car.app.CarActivityManager; +import android.car.builtin.os.UserManagerHelper; +import android.car.builtin.util.Slogf; +import android.car.builtin.view.DisplayHelper; +import android.car.builtin.window.DisplayAreaOrganizerHelper; +import android.content.ComponentName; +import android.hardware.display.DisplayManager; +import android.os.ServiceSpecificException; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseIntArray; +import android.view.Display; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Implementation of {@link CarLaunchParamsModifierUpdatable}. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class CarLaunchParamsModifierUpdatableImpl + implements CarLaunchParamsModifierUpdatable { + private static final String TAG = "CAR.LAUNCH"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + + private final CarLaunchParamsModifierInterface mBuiltin; + private final Object mLock = new Object(); + + // Always start with USER_SYSTEM as the timing of handleCurrentUserSwitching(USER_SYSTEM) is not + // guaranteed to be earler than 1st Activity launch. + @GuardedBy("mLock") + private int mCurrentDriverUser = UserManagerHelper.USER_SYSTEM; + + // TODO: Switch from tracking displays to tracking display areas instead + /** + * This one is for holding all passenger (=profile user) displays which are mostly static unless + * displays are added / removed. Note that {@link #mDisplayToProfileUserMapping} can be empty + * while user is assigned and that cannot always tell if specific display is for driver or not. + */ + @GuardedBy("mLock") + private final ArrayList<Integer> mPassengerDisplays = new ArrayList<>(); + + /** key: display id, value: profile user id */ + @GuardedBy("mLock") + private final SparseIntArray mDisplayToProfileUserMapping = new SparseIntArray(); + + /** key: profile user id, value: display id */ + @GuardedBy("mLock") + private final SparseIntArray mDefaultDisplayForProfileUser = new SparseIntArray(); + + @GuardedBy("mLock") + private boolean mIsSourcePreferred; + + @GuardedBy("mLock") + private List<ComponentName> mSourcePreferredComponents; + + /** key: Activity, value: TaskDisplayAreaWrapper */ + @GuardedBy("mLock") + private final ArrayMap<ComponentName, TaskDisplayAreaWrapper> mPersistentActivities = + new ArrayMap<>(); + + public CarLaunchParamsModifierUpdatableImpl(CarLaunchParamsModifierInterface builtin) { + mBuiltin = builtin; + } + + public DisplayManager.DisplayListener getDisplayListener() { + return mDisplayListener; + } + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + // ignore. car service should update whiltelist. + } + + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + mPassengerDisplays.remove(Integer.valueOf(displayId)); + updateProfileUserConfigForDisplayRemovalLocked(displayId); + } + } + + @Override + public void onDisplayChanged(int displayId) { + // ignore + } + }; + + @GuardedBy("mLock") + private void updateProfileUserConfigForDisplayRemovalLocked(int displayId) { + mDisplayToProfileUserMapping.delete(displayId); + int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); + if (i >= 0) { + mDefaultDisplayForProfileUser.removeAt(i); + } + } + + /** + * Sets {@code sourcePreferred} configuration. When {@code sourcePreferred} is enabled and + * there is no pre-assigned display for the Activity, CarLauncherParamsModifier will launch + * the Activity in the display of the source. When {@code sourcePreferredComponents} isn't null + * the {@code sourcePreferred} is applied for the {@code sourcePreferredComponents} only. + * + * @param enableSourcePreferred whether to enable sourcePreferred mode + * @param sourcePreferredComponents null for all components, or the list of components to apply + */ + public void setSourcePreferredComponents(boolean enableSourcePreferred, + @Nullable List<ComponentName> sourcePreferredComponents) { + synchronized (mLock) { + mIsSourcePreferred = enableSourcePreferred; + mSourcePreferredComponents = sourcePreferredComponents; + if (mSourcePreferredComponents != null) { + Collections.sort(mSourcePreferredComponents); + } + } + } + + /** Notifies user starting. */ + public void handleUserStarting(int startingUser) { + // Do nothing + } + + /** Notifies user switching. */ + public void handleCurrentUserSwitching(@UserIdInt int newUserId) { + synchronized (mLock) { + mCurrentDriverUser = newUserId; + mDefaultDisplayForProfileUser.clear(); + mDisplayToProfileUserMapping.clear(); + } + } + + @GuardedBy("mLock") + private void removeUserFromAllowlistsLocked(int userId) { + for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) { + if (mDisplayToProfileUserMapping.valueAt(i) == userId) { + mDisplayToProfileUserMapping.removeAt(i); + } + } + mDefaultDisplayForProfileUser.delete(userId); + } + + /** Notifies user stopped. */ + public void handleUserStopped(@UserIdInt int stoppedUser) { + // Note that the current user is never stopped. It always takes switching into + // non-current user before stopping the user. + synchronized (mLock) { + removeUserFromAllowlistsLocked(stoppedUser); + } + } + + /** + * Sets display allowlist for the {@code userId}. For passenger user, activity will be always + * launched to a display in the allowlist. If requested display is not in the allowlist, the 1st + * display in the allowlist will be selected as target display. + * + * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users + * for the given displays. + */ + public void setDisplayAllowListForUser(@UserIdInt int userId, int[] displayIds) { + if (DBG) { + Slogf.d(TAG, "setDisplayAllowlistForUser userId:%d displays:%s", + userId, Arrays.toString(displayIds)); + } + synchronized (mLock) { + for (int displayId : displayIds) { + if (!mPassengerDisplays.contains(displayId)) { + Slogf.w(TAG, "setDisplayAllowlistForUser called with display:%d" + + " not in passenger display list:%s", displayId, mPassengerDisplays); + continue; + } + if (userId == mCurrentDriverUser) { + mDisplayToProfileUserMapping.delete(displayId); + } else { + mDisplayToProfileUserMapping.put(displayId, userId); + } + // now the display cannot be a default display for other user + int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); + if (i >= 0) { + mDefaultDisplayForProfileUser.removeAt(i); + } + } + if (displayIds.length > 0) { + mDefaultDisplayForProfileUser.put(userId, displayIds[0]); + } else { + removeUserFromAllowlistsLocked(userId); + } + } + } + + /** + * Sets displays assigned to passenger. All other displays will be treated as assigned to + * driver. + * + * <p>The 1st display in the array will be considered as a default display to assign + * for any non-driver user if there is no display assigned for the user. </p> + */ + public void setPassengerDisplays(int[] displayIdsForPassenger) { + if (DBG) { + Slogf.d(TAG, "setPassengerDisplays displays:%s", + Arrays.toString(displayIdsForPassenger)); + } + synchronized (mLock) { + for (int id : displayIdsForPassenger) { + mPassengerDisplays.remove(Integer.valueOf(id)); + } + // handle removed displays + for (int i = 0; i < mPassengerDisplays.size(); i++) { + int displayId = mPassengerDisplays.get(i); + updateProfileUserConfigForDisplayRemovalLocked(displayId); + } + mPassengerDisplays.clear(); + mPassengerDisplays.ensureCapacity(displayIdsForPassenger.length); + for (int id : displayIdsForPassenger) { + mPassengerDisplays.add(id); + } + } + } + + /** + * Calculates {@code outParams} based on the given arguments. + * See {@code LaunchParamsController.LaunchParamsModifier.onCalculate()} for the detail. + */ + public int calculate(CalculateParams params) { + TaskWrapper task = params.getTask(); + ActivityRecordWrapper activity = params.getActivity(); + ActivityRecordWrapper source = params.getSource(); + ActivityOptionsWrapper options = params.getOptions(); + RequestWrapper request = params.getRequest(); + LaunchParamsWrapper currentParams = params.getCurrentParams(); + LaunchParamsWrapper outParams = params.getOutParams(); + + int userId; + if (task != null) { + userId = task.getUserId(); + } else if (activity != null) { + userId = activity.getUserId(); + } else { + Slogf.w(TAG, "onCalculate, cannot decide user"); + return LaunchParamsWrapper.RESULT_SKIP; + } + // DisplayArea where user wants to launch the Activity. + TaskDisplayAreaWrapper originalDisplayArea = currentParams.getPreferredTaskDisplayArea(); + // DisplayArea where CarLaunchParamsModifier targets to launch the Activity. + TaskDisplayAreaWrapper targetDisplayArea = null; + if (DBG) { + Slogf.d(TAG, "onCalculate, userId:%d original displayArea:%s ActivityOptions:%s", + userId, originalDisplayArea, options); + } + ComponentName activityName = activity.getComponentName(); + decision: + synchronized (mLock) { + // If originalDisplayArea is set, respect that before ActivityOptions check. + if (originalDisplayArea == null) { + if (options != null) { + originalDisplayArea = options.getLaunchTaskDisplayArea(); + if (originalDisplayArea == null) { + originalDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( + options.getOptions().getLaunchDisplayId()); + } + } + } + if (mPersistentActivities.containsKey(activityName)) { + targetDisplayArea = mPersistentActivities.get(activityName); + } else if (originalDisplayArea == null // No specified DA to launch the Activity + && mIsSourcePreferred && source != null + && (mSourcePreferredComponents == null || Collections.binarySearch( + mSourcePreferredComponents, activityName) >= 0)) { + targetDisplayArea = source.isNoDisplay() ? source.getHandoverTaskDisplayArea() + : source.getDisplayArea(); + } else if (originalDisplayArea == null + && task == null // launching as a new task + && source != null && !source.isDisplayTrusted() + && !source.allowingEmbedded()) { + if (DBG) { + Slogf.d(TAG, "Disallow launch on virtual display for not-embedded activity."); + } + targetDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( + Display.DEFAULT_DISPLAY); + } + if (userId == mCurrentDriverUser) { + // Respect the existing DisplayArea. + break decision; + } + if (userId == UserManagerHelper.USER_SYSTEM) { + // This will be only allowed if it has FLAG_SHOW_FOR_ALL_USERS. + // The flag is not immediately accessible here so skip the check. + // But other WM policy will enforce it. + break decision; + } + // Now user is a passenger. + if (mPassengerDisplays.isEmpty()) { + // No displays for passengers. This could be old user and do not do anything. + break decision; + } + if (targetDisplayArea == null) { + if (originalDisplayArea != null) { + targetDisplayArea = originalDisplayArea; + } else { + targetDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( + Display.DEFAULT_DISPLAY); + } + } + Display display = targetDisplayArea.getDisplay(); + if ((display.getFlags() & Display.FLAG_PRIVATE) != 0) { + // private display should follow its own restriction rule. + break decision; + } + if (DisplayHelper.getType(display) == DisplayHelper.TYPE_VIRTUAL) { + // TODO(b/132903422) : We need to update this after the bug is resolved. + // For now, don't change anything. + break decision; + } + int userForDisplay = mDisplayToProfileUserMapping.get(display.getDisplayId(), + UserManagerHelper.USER_NULL); + if (userForDisplay == userId) { + break decision; + } + targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked( + userId, activity, request); + } + if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) { + Slogf.i(TAG, "Changed launching display, user:%d requested display area:%s" + + " target display area:", userId, originalDisplayArea, targetDisplayArea); + outParams.setPreferredTaskDisplayArea(targetDisplayArea); + return LaunchParamsWrapper.RESULT_DONE; + } else { + return LaunchParamsWrapper.RESULT_SKIP; + } + } + + @GuardedBy("mLock") + @Nullable + private TaskDisplayAreaWrapper getAlternativeDisplayAreaForPassengerLocked(int userId, + @NonNull ActivityRecordWrapper activtyRecord, @Nullable RequestWrapper request) { + List<TaskDisplayAreaWrapper> fallbacks = mBuiltin.getFallbackDisplayAreasForActivity( + activtyRecord, request); + for (int i = 0, size = fallbacks.size(); i < size; ++i) { + TaskDisplayAreaWrapper fallbackTda = fallbacks.get(i); + int userForDisplay = getUserIdForDisplayLocked(fallbackTda.getDisplay().getDisplayId()); + if (userForDisplay == userId) { + return fallbackTda; + } + } + return fallbackDisplayAreaForUserLocked(userId); + } + + /** + * Returns {@code userId} who is allowed to use the given {@code displayId}, or + * {@code UserHandle.USER_NULL} if the display doesn't exist in the mapping. + */ + @GuardedBy("mLock") + private int getUserIdForDisplayLocked(int displayId) { + return mDisplayToProfileUserMapping.get(displayId, UserManagerHelper.USER_NULL); + } + + /** + * Return a {@link TaskDisplayAreaWrapper} that can be used if a source display area is + * not found. First check the default display for the user. If it is absent select + * the first passenger display if present. If both are absent return {@code null} + * + * @param userId ID of the active user + * @return {@link TaskDisplayAreaWrapper} that is recommended when a display area is + * not specified + */ + @GuardedBy("mLock") + @Nullable + private TaskDisplayAreaWrapper fallbackDisplayAreaForUserLocked(@UserIdInt int userId) { + int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId, + Display.INVALID_DISPLAY); + if (displayIdForUserProfile != Display.INVALID_DISPLAY) { + int displayId = mDefaultDisplayForProfileUser.get(userId); + return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId); + } + if (!mPassengerDisplays.isEmpty()) { + int displayId = mPassengerDisplays.get(0); + return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId); + } + return null; + } + + /** + * See {@link CarActivityManager#setPersistentActivity(android.content.ComponentName,int, int)} + */ + public int setPersistentActivity(ComponentName activity, int displayId, int featureId) { + if (DBG) { + Slogf.d(TAG, "setPersistentActivity: activity=%s, displayId=%d, featureId=%d", + activity, displayId, featureId); + } + if (featureId == DisplayAreaOrganizerHelper.FEATURE_UNDEFINED) { + synchronized (mLock) { + TaskDisplayAreaWrapper removed = mPersistentActivities.remove(activity); + if (removed == null) { + throw new ServiceSpecificException( + CarActivityManager.ERROR_CODE_ACTIVITY_NOT_FOUND, + "Failed to remove " + activity.toShortString()); + } + return CarActivityManager.RESULT_SUCCESS; + } + } + TaskDisplayAreaWrapper tda = mBuiltin.findTaskDisplayArea(displayId, featureId); + if (tda == null) { + throw new IllegalArgumentException("Unknown display=" + displayId + + " or feature=" + featureId); + } + synchronized (mLock) { + mPersistentActivities.put(activity, tda); + } + return CarActivityManager.RESULT_SUCCESS; + } +}
\ No newline at end of file diff --git a/updatableServices/tests/Android.bp b/updatableServices/tests/Android.bp new file mode 100644 index 0000000..7d9e971 --- /dev/null +++ b/updatableServices/tests/Android.bp @@ -0,0 +1,50 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "Android-Apache-2.0", + ], +} + +android_test { + name: "FrameworkOptCarServicesUpdatableTest", + + srcs: [ + "src/**/*.java", + ], + + platform_apis: true, + + certificate: "platform", + + optimize: { + enabled: false, + }, + + libs: [ + "android.car", + "android.car.builtin", + "android.test.runner", + "android.test.base", + "android.hardware.automotive.vehicle-V2.0-java", + ], + + static_libs: [ + "android.car.test.utils", + "android.car.watchdoglib", + "androidx.test.ext.junit", + "androidx.test.rules", + "car-frameworks-service.impl", + "car-frameworks-service-module", + "mockito-target-extended-minus-junit4", + "services.core", + "testng", + "truth-prebuilt", + ], + + // mockito-target-extended dependencies + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + +} diff --git a/updatableServices/tests/AndroidManifest.xml b/updatableServices/tests/AndroidManifest.xml new file mode 100644 index 0000000..2a3e07f --- /dev/null +++ b/updatableServices/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.internal.car.updatable" + android:sharedUserId="android.uid.system" > + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.internal.car.updatable" + android:label="Unit Tests for Car Framework Updatable Services"/> + + <application android:label="FrameworkOptCarServicesUpdatableTest" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> +</manifest> diff --git a/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java b/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java new file mode 100644 index 0000000..6e5231b --- /dev/null +++ b/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceHelperServiceUpdatableImplTest.java @@ -0,0 +1,180 @@ +/* + * 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 com.android.internal.car.updatable; + +import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; + +import android.car.ICar; +import android.car.builtin.os.UserManagerHelper; +import android.car.test.mocks.AbstractExtendedMockitoTestCase; +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.car.CarServiceHelperInterface; +import com.android.server.wm.CarLaunchParamsModifierInterface; + +import java.util.function.BiConsumer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** + * This class contains unit tests for the {@link CarServiceHelperServiceUpdatableImpl}. + */ +@RunWith(AndroidJUnit4.class) +public final class CarServiceHelperServiceUpdatableImplTest + extends AbstractExtendedMockitoTestCase { + + @Mock + private Context mMockContext; + @Mock + private CarServiceProxy mCarServiceProxy; + @Mock + private CarServiceHelperInterface mCarServiceHelperInterface; + @Mock + private CarLaunchParamsModifierInterface mCarLaunchParamsModifierInterface; + @Mock + private ICar mICarBinder; + @Mock + private IBinder mIBinder; + + private CarServiceHelperServiceUpdatableImpl mCarServiceHelperServiceUpdatableImpl; + + public CarServiceHelperServiceUpdatableImplTest() { + super(CarServiceHelperServiceUpdatableImpl.TAG); + } + + @Before + public void setTestFixtures() { + mCarServiceHelperServiceUpdatableImpl = new CarServiceHelperServiceUpdatableImpl( + mMockContext, + mCarServiceHelperInterface, + mCarLaunchParamsModifierInterface, + mCarServiceProxy); + } + + @Test + public void testCarServiceLaunched() throws Exception { + mockSystemContext(); + mockBindService(); + + mCarServiceHelperServiceUpdatableImpl.onStart(); + + verifyBindService(); + } + + @Test + public void testHandleCarServiceConnection() throws Exception { + mockICarBinder(); + + mCarServiceHelperServiceUpdatableImpl.handleCarServiceConnection(mIBinder); + + verify(mICarBinder).setSystemServerConnections(any(), any()); + } + + @Test + public void testHandleCarServiceCrash() throws Exception { + mockICarBinder(); + doThrow(new RemoteException()).when(mICarBinder).setSystemServerConnections(any(), any()); + + mCarServiceHelperServiceUpdatableImpl.handleCarServiceConnection(mIBinder); + + verify(mCarServiceHelperInterface).dumpServiceStacks(); + } + + @Test + public void testOnUserRemoved() throws Exception { + UserHandle user = UserHandle.of(101); + mCarServiceHelperServiceUpdatableImpl.onUserRemoved(user); + + verify(mCarServiceProxy).onUserRemoved(user); + } + + @Test + public void testOnFactoryReset() throws Exception { + BiConsumer<Integer, Bundle> callback = (x, y) -> {}; + mCarServiceHelperServiceUpdatableImpl.onFactoryReset(callback); + + verify(mCarServiceProxy).onFactoryReset(any()); + } + + @Test + public void testInitBootUser() throws Exception { + mCarServiceHelperServiceUpdatableImpl.initBootUser(); + + verify(mCarServiceProxy).initBootUser(); + } + + @Test + public void testSendUserLifecycleEvent_nullFromUser() throws Exception { + int eventType = 1; + UserHandle userFrom = null; + UserHandle userTo = UserHandle.SYSTEM; + + mCarServiceHelperServiceUpdatableImpl.sendUserLifecycleEvent(eventType, userFrom, userTo); + + verify(mCarServiceProxy).sendUserLifecycleEvent(eventType, UserManagerHelper.USER_NULL, + userTo.getIdentifier()); + } + + @Test + public void testSendUserLifecycleEvent() throws Exception { + int eventType = 1; + UserHandle userFrom = UserHandle.SYSTEM; + int userId = 101; + UserHandle userTo = UserHandle.of(userId); + + mCarServiceHelperServiceUpdatableImpl.sendUserLifecycleEvent(eventType, userFrom, userTo); + + verify(mCarServiceProxy).sendUserLifecycleEvent(eventType, userFrom.getIdentifier(), + userTo.getIdentifier()); + } + + private void mockICarBinder() { + when(ICar.Stub.asInterface(mIBinder)).thenReturn(mICarBinder); + } + + private void mockSystemContext() { + when(mMockContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0)) + .thenReturn(mMockContext); + } + + private void mockBindService() { + when(mMockContext.bindService(any(), eq(Context.BIND_AUTO_CREATE), any(), any())) + .thenReturn(true); + } + + private void verifyBindService() throws Exception { + verify(mMockContext).bindService( + argThat(intent -> intent.getAction().equals(CAR_SERVICE_INTERFACE)), + eq(Context.BIND_AUTO_CREATE), any(), any()); + } +} diff --git a/tests/src/com/android/internal/car/CarServiceProxyTest.java b/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceProxyTest.java index 74bc8b0..714cf55 100644 --- a/tests/src/com/android/internal/car/CarServiceProxyTest.java +++ b/updatableServices/tests/src/com/android/internal/car/updatable/CarServiceProxyTest.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - -package com.android.internal.car; +package com.android.internal.car.updatable; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; @@ -24,13 +22,13 @@ import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.car.ICarResultReceiver; import android.car.test.mocks.AbstractExtendedMockitoTestCase; import android.car.test.util.UserTestingHelper.UserInfoBuilder; import android.content.pm.UserInfo; import android.os.RemoteException; import com.android.car.internal.ICarSystemServerClient; -import com.android.internal.os.IResultReceiver; import com.android.server.SystemService.TargetUser; import org.junit.Before; @@ -40,15 +38,15 @@ import org.mockito.Mock; public class CarServiceProxyTest extends AbstractExtendedMockitoTestCase { @Mock - private CarServiceHelperService mCarServiceHelperService; + private CarServiceHelperServiceUpdatableImpl mCarServiceHelperServiceUpdatableImpl; @Mock private ICarSystemServerClient mCarService; @Mock - private IResultReceiver mFactoryResetCallback1; + private ICarResultReceiver mFactoryResetCallback1; @Mock - private IResultReceiver mFactoryResetCallback2; + private ICarResultReceiver mFactoryResetCallback2; private final TargetUser mFromUser = new TargetUser(new UserInfo(101, "fromUser", 0)); private final TargetUser mToUser = new TargetUser(new UserInfo(102, "toUser", 0)); @@ -59,9 +57,13 @@ public class CarServiceProxyTest extends AbstractExtendedMockitoTestCase { private CarServiceProxy mCarServiceProxy; + public CarServiceProxyTest() { + super(CarServiceProxy.TAG); + } + @Before public void setUpMocks() { - mCarServiceProxy = new CarServiceProxy(mCarServiceHelperService); + mCarServiceProxy = new CarServiceProxy(mCarServiceHelperServiceUpdatableImpl); } @Test @@ -170,16 +172,17 @@ public class CarServiceProxyTest extends AbstractExtendedMockitoTestCase { } private void callSendLifecycleEvent(int eventType) { - mCarServiceProxy.sendUserLifecycleEvent(eventType, mFromUser, mToUser); + mCarServiceProxy.sendUserLifecycleEvent(eventType, mFromUser.getUserIdentifier(), + mToUser.getUserIdentifier()); } private void callOnUserRemoved() { - mCarServiceProxy.onUserRemoved(mRemovedUser1); - mCarServiceProxy.onUserRemoved(mRemovedUser2); - mCarServiceProxy.onUserRemoved(mRemovedUser3); + mCarServiceProxy.onUserRemoved(mRemovedUser1.getUserHandle()); + mCarServiceProxy.onUserRemoved(mRemovedUser2.getUserHandle()); + mCarServiceProxy.onUserRemoved(mRemovedUser3.getUserHandle()); } - private void callOnFactoryReset(IResultReceiver callback) { + private void callOnFactoryReset(ICarResultReceiver callback) { mCarServiceProxy.onFactoryReset(callback); } @@ -201,20 +204,20 @@ public class CarServiceProxyTest extends AbstractExtendedMockitoTestCase { } private void verifyOnUserRemovedCalled() throws RemoteException { - verify(mCarService).onUserRemoved(mRemovedUser1); - verify(mCarService).onUserRemoved(mRemovedUser2); - verify(mCarService).onUserRemoved(mRemovedUser3); + verify(mCarService).onUserRemoved(mRemovedUser1.getUserHandle()); + verify(mCarService).onUserRemoved(mRemovedUser2.getUserHandle()); + verify(mCarService).onUserRemoved(mRemovedUser3.getUserHandle()); } private void verifyOnUserRemovedNeverCalled() throws RemoteException { verify(mCarService, never()).onUserRemoved(any()); } - private void verifyOnFactoryResetCalled(IResultReceiver callback) throws RemoteException { + private void verifyOnFactoryResetCalled(ICarResultReceiver callback) throws RemoteException { verify(mCarService).onFactoryReset(callback); } - private void verifyOnFactoryResetNotCalled(IResultReceiver callback) throws RemoteException { + private void verifyOnFactoryResetNotCalled(ICarResultReceiver callback) throws RemoteException { verify(mCarService, never()).onFactoryReset(callback); } diff --git a/tests/src/com/android/internal/car/UserMetricsTest.java b/updatableServices/tests/src/com/android/internal/car/updatable/UserMetricsTest.java index 67ed9f9..b778de4 100644 --- a/tests/src/com/android/internal/car/UserMetricsTest.java +++ b/updatableServices/tests/src/com/android/internal/car/updatable/UserMetricsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.car; +package com.android.internal.car.updatable; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING; import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED; @@ -31,8 +31,8 @@ import android.annotation.UserIdInt; import android.os.SystemClock; import android.util.SparseArray; -import com.android.internal.car.UserMetrics.UserStartingMetric; -import com.android.internal.car.UserMetrics.UserStoppingMetric; +import com.android.internal.car.updatable.UserMetrics.UserStartingMetric; +import com.android.internal.car.updatable.UserMetrics.UserStoppingMetric; import org.junit.Test; diff --git a/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java b/updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java index d641cff..13dc733 100644 --- a/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java +++ b/updatableServices/tests/src/com/android/server/wm/CarLaunchParamsModifierUpdatableTest.java @@ -25,33 +25,46 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertThrows; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; +import android.car.app.CarActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.res.Configuration; import android.hardware.display.DisplayManager; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.view.Display; import android.view.SurfaceControl; +import android.window.DisplayAreaOrganizer; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.policy.AttributeCache; import com.android.server.LocalServices; import com.android.server.display.color.ColorDisplayService; +import com.android.server.input.InputManagerService; +import com.android.server.policy.WindowManagerPolicy; import org.junit.After; import org.junit.Before; @@ -62,24 +75,30 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.util.Arrays; +import java.util.function.Function; /** * Tests for {@link CarLaunchParamsModifier} * Build/Install/Run: - * atest CarServicesTest:CarLaunchParamsModifierTest + * atest FrameworkOptCarServicesUpdatableTest:CarLaunchParamsModifierUpdatableTest */ @RunWith(AndroidJUnit4.class) -public class CarLaunchParamsModifierTest { +public class CarLaunchParamsModifierUpdatableTest { private static final int PASSENGER_DISPLAY_ID_10 = 10; private static final int PASSENGER_DISPLAY_ID_11 = 11; private static final int VIRTUAL_DISPLAY_ID_2 = 2; + private static final int FEATURE_MAP_ID = 1111; private MockitoSession mMockingSession; private CarLaunchParamsModifier mModifier; + private CarLaunchParamsModifierUpdatableImpl mUpdatable; + private CarLaunchParamsModifierInterface mBuiltin; - @Mock private Context mContext; + private WindowManagerService mWindowManagerService; + private final WindowManagerGlobalLock mWindowManagerGlobalLock = new WindowManagerGlobalLock(); + @Mock private DisplayManager mDisplayManager; @Mock @@ -89,13 +108,15 @@ public class CarLaunchParamsModifierTest { @Mock private RecentTasks mRecentTasks; @Mock - private WindowManagerService mWindowManagerService; - @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternal; @Mock private RootWindowContainer mRootWindowContainer; @Mock private LaunchParamsController mLaunchParamsController; + @Mock + private PackageConfigPersister mPackageConfigPersister; + @Mock + private InputManagerService mInputManagerService; @Mock private Display mDisplay0ForDriver; @@ -117,6 +138,7 @@ public class CarLaunchParamsModifierTest { private Display mDisplay2Virtual; @Mock private TaskDisplayArea mDisplayArea2Virtual; + private TaskDisplayArea mMapTaskDisplayArea; // All mocks from here before CarLaunchParamsModifier are arguments for // LaunchParamsModifier.onCalculate() call. @@ -145,6 +167,7 @@ public class CarLaunchParamsModifierTest { // Return the same id as the display for simplicity DisplayContent dc = mock(DisplayContent.class); defaultTaskDisplayArea.mDisplayContent = dc; + when(defaultTaskDisplayArea.getDisplayContent()).thenReturn(dc); when(mRootWindowContainer.getDisplayContentOrCreate(displayId)).thenReturn(dc); when(dc.getDisplay()).thenReturn(display); when(dc.getDefaultTaskDisplayArea()).thenReturn(defaultTaskDisplayArea); @@ -158,21 +181,46 @@ public class CarLaunchParamsModifierTest { .mockStatic(ActivityTaskManager.class) .strictness(Strictness.LENIENT) .startMocking(); - when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager); + mContext = getInstrumentation().getTargetContext(); + spyOn(mContext); + doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); + doReturn(mActivityTaskManagerService).when(() -> ActivityTaskManager.getService()); mActivityTaskManagerService.mTaskSupervisor = mActivityTaskSupervisor; when(mActivityTaskSupervisor.getLaunchParamsController()).thenReturn( mLaunchParamsController); + mActivityTaskManagerService.mSupportsMultiDisplay = true; mActivityTaskManagerService.mRootWindowContainer = mRootWindowContainer; - mActivityTaskManagerService.mWindowManager = mWindowManagerService; + mActivityTaskManagerService.mPackageConfigPersister = mPackageConfigPersister; + mActivityTaskManagerService.mWindowOrganizerController = + new WindowOrganizerController(mActivityTaskManagerService); + when(mActivityTaskManagerService.getTransitionController()).thenCallRealMethod(); when(mActivityTaskManagerService.getRecentTasks()).thenReturn(mRecentTasks); - mWindowManagerService.mTransactionFactory = () -> new SurfaceControl.Transaction(); + when(mActivityTaskManagerService.getGlobalLock()).thenReturn(mWindowManagerGlobalLock); + + mWindowManagerService = WindowManagerService.main( + mContext, mInputManagerService, /* showBootMsgs= */ false, /* onlyCore= */ false, + /* policy= */ null, mActivityTaskManagerService, + /* displayWindowSettingsProvider= */ null, () -> new SurfaceControl.Transaction(), + /* surfaceFactory= */ null, /* surfaceControlFactory= */ null); + mActivityTaskManagerService.mWindowManager = mWindowManagerService; + mRootWindowContainer.mWindowManager = mWindowManagerService; + AttributeCache.init(getInstrumentation().getTargetContext()); LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class, mColorDisplayServiceInternal); when(mActivityOptions.getLaunchDisplayId()).thenReturn(INVALID_DISPLAY); mockDisplay(mDisplay0ForDriver, mDisplayArea0ForDriver, DEFAULT_DISPLAY, FLAG_TRUSTED, /* type= */ 0); + DisplayContent defaultDC = mRootWindowContainer.getDisplayContentOrCreate(DEFAULT_DISPLAY); + mMapTaskDisplayArea = new TaskDisplayArea( + defaultDC, mWindowManagerService, "MapTDA", FEATURE_MAP_ID); + doAnswer((invocation) -> { + Function<TaskDisplayArea, TaskDisplayArea> callback = invocation.getArgument(0); + return callback.apply(mMapTaskDisplayArea); + }).when(defaultDC).getItemFromTaskDisplayAreas(any()); + when(mActivityRecordSource.getDisplayContent()).thenReturn(defaultDC); + mockDisplay(mDisplay10ForPassenger, mDisplayArea10ForPassenger, PASSENGER_DISPLAY_ID_10, FLAG_TRUSTED, /* type= */ 0); mockDisplay(mDisplay11ForPassenger, mDisplayArea11ForPassenger, PASSENGER_DISPLAY_ID_11, @@ -181,23 +229,26 @@ public class CarLaunchParamsModifierTest { FLAG_TRUSTED | FLAG_PRIVATE, /* type= */ 0); mockDisplay(mDisplay2Virtual, mDisplayArea2Virtual, VIRTUAL_DISPLAY_ID_2, FLAG_PRIVATE, /* type= */ 0); - DisplayContent defaultDc = mRootWindowContainer.getDisplayContentOrCreate(DEFAULT_DISPLAY); - when(mActivityRecordSource.getDisplayContent()).thenReturn(defaultDc); mModifier = new CarLaunchParamsModifier(mContext); + mBuiltin = mModifier.getBuiltinInterface(); + mUpdatable = new CarLaunchParamsModifierUpdatableImpl(mBuiltin); + mModifier.setUpdatable(mUpdatable); mModifier.init(); } @After public void tearDown() { + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.removeServiceForTest(WindowManagerPolicy.class); LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); mMockingSession.finishMocking(); } private void assertDisplayIsAllowed(@UserIdInt int userId, Display display) { mTask.mUserId = userId; - mCurrentParams.mPreferredTaskDisplayArea = mModifier - .getDefaultTaskDisplayAreaOnDisplay(display.getDisplayId()); + mCurrentParams.mPreferredTaskDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( + display.getDisplayId()).getTaskDisplayArea(); assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity, mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams, mOutParams)) @@ -208,10 +259,10 @@ public class CarLaunchParamsModifierTest { Display displayAssigned) { assertThat(displayRequested.getDisplayId()).isNotEqualTo(displayAssigned.getDisplayId()); mTask.mUserId = userId; - TaskDisplayArea requestedTaskDisplayArea = mModifier - .getDefaultTaskDisplayAreaOnDisplay(displayRequested.getDisplayId()); - TaskDisplayArea assignedTaskDisplayArea = mModifier - .getDefaultTaskDisplayAreaOnDisplay(displayAssigned.getDisplayId()); + TaskDisplayArea requestedTaskDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( + displayRequested.getDisplayId()).getTaskDisplayArea(); + TaskDisplayArea assignedTaskDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( + displayAssigned.getDisplayId()).getTaskDisplayArea(); mCurrentParams.mPreferredTaskDisplayArea = requestedTaskDisplayArea; assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity, mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams, @@ -255,9 +306,14 @@ public class CarLaunchParamsModifierTest { return new ActivityRecord.Builder(mActivityTaskManagerService) .setIntent(intent) .setActivityInfo(info) + .setConfiguration(new Configuration()) .build(); } + private ActivityRecord buildActivityRecord(ComponentName componentName) { + return buildActivityRecord(componentName.getPackageName(), componentName.getClassName()); + } + @Test public void testNoPolicySet() { final int randomUserId = 1000; @@ -280,7 +336,7 @@ public class CarLaunchParamsModifierTest { @Test public void testAllowAllForDriverDuringBoot() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); // USER_SYSTEM should be allowed always @@ -289,7 +345,7 @@ public class CarLaunchParamsModifierTest { @Test public void testAllowAllForDriverAfterUserSwitching() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); final int driver1 = 10; @@ -305,11 +361,11 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerAllowed() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); final int passengerUserId = 100; - mModifier.setDisplayAllowListForUser(passengerUserId, + mUpdatable.setDisplayAllowListForUser(passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger); @@ -317,17 +373,17 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerChange() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); int passengerUserId1 = 100; - mModifier.setDisplayAllowListForUser(passengerUserId1, + mUpdatable.setDisplayAllowListForUser(passengerUserId1, new int[]{mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId1, mDisplay11ForPassenger); int passengerUserId2 = 101; - mModifier.setDisplayAllowListForUser(passengerUserId2, + mUpdatable.setDisplayAllowListForUser(passengerUserId2, new int[]{mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId2, mDisplay11ForPassenger); @@ -337,11 +393,11 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerNotAllowed() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); final int passengerUserId = 100; - mModifier.setDisplayAllowListForUser( + mUpdatable.setDisplayAllowListForUser( passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId()}); assertDisplayIsReassigned(passengerUserId, mDisplay0ForDriver, mDisplay10ForPassenger); @@ -350,11 +406,11 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerNotAllowedAfterUserSwitch() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); int passengerUserId = 100; - mModifier.setDisplayAllowListForUser( + mUpdatable.setDisplayAllowListForUser( passengerUserId, new int[]{mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger); @@ -366,15 +422,15 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerNotAllowedAfterAssigningCurrentUser() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); int passengerUserId = 100; - mModifier.setDisplayAllowListForUser( + mUpdatable.setDisplayAllowListForUser( passengerUserId, new int[]{mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger); - mModifier.setDisplayAllowListForUser( + mUpdatable.setDisplayAllowListForUser( UserHandle.USER_SYSTEM, new int[]{mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsReassigned(passengerUserId, mDisplay0ForDriver, mDisplay10ForPassenger); @@ -383,18 +439,18 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerDisplayRemoved() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); final int passengerUserId = 100; - mModifier.setDisplayAllowListForUser(passengerUserId, + mUpdatable.setDisplayAllowListForUser(passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger); assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger); - mModifier.mDisplayListener.onDisplayRemoved(mDisplay11ForPassenger.getDisplayId()); + mUpdatable.getDisplayListener().onDisplayRemoved(mDisplay11ForPassenger.getDisplayId()); assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger); assertDisplayIsReassigned(passengerUserId, mDisplay11ForPassenger, mDisplay10ForPassenger); @@ -402,18 +458,18 @@ public class CarLaunchParamsModifierTest { @Test public void testPassengerDisplayRemovedFromSetPassengerDisplays() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); final int passengerUserId = 100; - mModifier.setDisplayAllowListForUser(passengerUserId, + mUpdatable.setDisplayAllowListForUser(passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger); assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger); - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId()}); + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId()}); assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger); assertDisplayIsReassigned(passengerUserId, mDisplay11ForPassenger, mDisplay10ForPassenger); @@ -421,11 +477,11 @@ public class CarLaunchParamsModifierTest { @Test public void testIgnorePrivateDisplay() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); final int passengerUserId = 100; - mModifier.setDisplayAllowListForUser(passengerUserId, + mUpdatable.setDisplayAllowListForUser(passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); @@ -434,13 +490,13 @@ public class CarLaunchParamsModifierTest { @Test public void testDriverPassengerSwap() { - mModifier.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); final int wasDriver = 10; final int wasPassenger = 11; mModifier.handleCurrentUserSwitching(wasDriver); - mModifier.setDisplayAllowListForUser(wasPassenger, + mUpdatable.setDisplayAllowListForUser(wasPassenger, new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); @@ -454,7 +510,7 @@ public class CarLaunchParamsModifierTest { final int driver = wasPassenger; final int passenger = wasDriver; mModifier.handleCurrentUserSwitching(driver); - mModifier.setDisplayAllowListForUser(passenger, + mUpdatable.setDisplayAllowListForUser(passenger, new int[]{mDisplay10ForPassenger.getDisplayId(), mDisplay11ForPassenger.getDisplayId()}); @@ -473,22 +529,23 @@ public class CarLaunchParamsModifierTest { // When no sourcePreferredComponents is set, it doesn't set the display for system user. assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM); - mModifier.setSourcePreferredComponents(true, null); + mUpdatable.setSourcePreferredComponents(true, null); assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mDisplayArea0ForDriver); } @Test public void testPreferSourceForPassenger() { - mModifier.setPassengerDisplays(new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11}); + mUpdatable.setPassengerDisplays( + new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11}); int passengerUserId = 100; - mModifier.setDisplayAllowListForUser(passengerUserId, + mUpdatable.setDisplayAllowListForUser(passengerUserId, new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11}); when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea11ForPassenger); // When no sourcePreferredComponents is set, it returns the default passenger display. assertDisplayIsAssigned(passengerUserId, mDisplayArea10ForPassenger); - mModifier.setSourcePreferredComponents(true, null); + mUpdatable.setSourcePreferredComponents(true, null); assertDisplayIsAssigned(passengerUserId, mDisplayArea11ForPassenger); } @@ -497,7 +554,7 @@ public class CarLaunchParamsModifierTest { when(mActivityOptions.getLaunchDisplayId()).thenReturn(PASSENGER_DISPLAY_ID_10); when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver); - mModifier.setSourcePreferredComponents(true, null); + mUpdatable.setSourcePreferredComponents(true, null); assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM); } @@ -505,7 +562,7 @@ public class CarLaunchParamsModifierTest { public void testPreferSourceForSpecifiedActivity() { when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver); mActivityRecordActivity = buildActivityRecord("testPackage", "testActivity"); - mModifier.setSourcePreferredComponents(true, + mUpdatable.setSourcePreferredComponents(true, Arrays.asList(new ComponentName("testPackage", "testActivity"))); assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mDisplayArea0ForDriver); @@ -515,7 +572,7 @@ public class CarLaunchParamsModifierTest { public void testPreferSourceDoNotAssignDisplayForNonSpecifiedActivity() { when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver); mActivityRecordActivity = buildActivityRecord("placeholderPackage", "placeholderActivity"); - mModifier.setSourcePreferredComponents(true, + mUpdatable.setSourcePreferredComponents(true, Arrays.asList(new ComponentName("testPackage", "testActivity"))); assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM); @@ -565,9 +622,9 @@ public class CarLaunchParamsModifierTest { .thenReturn(processName); when(mActivityRecordActivity.getUid()) .thenReturn(processUid); - mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); - mModifier.setDisplayAllowListForUser(userId, + mUpdatable.setDisplayAllowListForUser(userId, new int[]{mDisplay10ForPassenger.getDisplayId()}); WindowProcessController controller = mock(WindowProcessController.class); when(mActivityTaskManagerService.getProcessController(processName, processUid)) @@ -593,9 +650,9 @@ public class CarLaunchParamsModifierTest { .thenReturn(launchedFromPid); when(mActivityRecordActivity.getLaunchedFromUid()) .thenReturn(launchedFromUid); - mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); - mModifier.setDisplayAllowListForUser(userId, + mUpdatable.setDisplayAllowListForUser(userId, new int[]{mDisplay10ForPassenger.getDisplayId()}); WindowProcessController controller = mock(WindowProcessController.class); when(mActivityTaskManagerService.getProcessController(launchedFromPid, launchedFromUid)) @@ -616,9 +673,9 @@ public class CarLaunchParamsModifierTest { public void testSourceDisplayFromCallingDisplayIfAvailable() { int userId = 10; ActivityStarter.Request request = fakeRequest(); - mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); - mModifier.setDisplayAllowListForUser(userId, + mUpdatable.setDisplayAllowListForUser(userId, new int[]{mDisplay10ForPassenger.getDisplayId()}); WindowProcessController controller = mock(WindowProcessController.class); when(mActivityTaskManagerService.getProcessController(request.realCallingPid, @@ -639,7 +696,7 @@ public class CarLaunchParamsModifierTest { @Test public void testSourceDisplayIgnoredIfNotInAllowList() { ActivityStarter.Request request = fakeRequest(); - mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), + mUpdatable.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(), mDisplay10ForPassenger.getDisplayId()}); WindowProcessController controller = mock(WindowProcessController.class); when(mActivityTaskManagerService.getProcessController(anyString(), anyInt())) @@ -658,7 +715,64 @@ public class CarLaunchParamsModifierTest { .isEqualTo(mDisplayArea11ForPassenger); } - private ActivityStarter.Request fakeRequest() { + @Test + public void testSetPersistentActivityThrowsExceptionForInvalidDisplayId() { + ComponentName mapActivity = new ComponentName("testMapPkg", "mapActivity"); + int invalidDisplayId = 999990; + + assertThrows(IllegalArgumentException.class, + () -> mUpdatable.setPersistentActivity(mapActivity, + invalidDisplayId, DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER)); + } + + @Test + public void testSetPersistentActivityThrowsExceptionForInvalidFeatureId() { + ComponentName mapActivity = new ComponentName("testMapPkg", "mapActivity"); + int invalidFeatureId = 999990; + + assertThrows(IllegalArgumentException.class, + () -> mUpdatable.setPersistentActivity(mapActivity, + DEFAULT_DISPLAY, invalidFeatureId)); + } + + @Test + public void testPersistentActivityOverridesTDA() { + ComponentName mapActivityName = new ComponentName("testMapPkg", "mapActivity"); + mActivityRecordActivity = buildActivityRecord(mapActivityName); + + int ret = mUpdatable.setPersistentActivity( + mapActivityName, DEFAULT_DISPLAY, FEATURE_MAP_ID); + assertThat(ret).isEqualTo(CarActivityManager.RESULT_SUCCESS); + + assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mMapTaskDisplayArea); + } + + @Test + public void testRemovePersistentActivity() { + ComponentName mapActivityName = new ComponentName("testMapPkg", "mapActivity"); + mActivityRecordActivity = buildActivityRecord(mapActivityName); + + int ret = mUpdatable.setPersistentActivity( + mapActivityName, DEFAULT_DISPLAY, FEATURE_MAP_ID); + assertThat(ret).isEqualTo(CarActivityManager.RESULT_SUCCESS); + // Removes the existing persistent Activity assignment. + ret = mUpdatable.setPersistentActivity(mapActivityName, DEFAULT_DISPLAY, + DisplayAreaOrganizer.FEATURE_UNDEFINED); + assertThat(ret).isEqualTo(CarActivityManager.RESULT_SUCCESS); + + assertNoDisplayIsAssigned(UserHandle.USER_SYSTEM); + } + + @Test + public void testRemoveUnknownPersistentActivityThrowsException() { + ComponentName mapActivity = new ComponentName("testMapPkg", "mapActivity"); + + assertThrows(ServiceSpecificException.class, + () -> mUpdatable.setPersistentActivity(mapActivity, DEFAULT_DISPLAY, + DisplayAreaOrganizer.FEATURE_UNDEFINED)); + } + + private static ActivityStarter.Request fakeRequest() { ActivityStarter.Request request = new ActivityStarter.Request(); request.realCallingPid = 1324; request.realCallingUid = 235; |